From 652990ecfe09e1493b4d59c3928bf3b8f23f2215 Mon Sep 17 00:00:00 2001 From: marcinm_backbase <63855724+bbzurek@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:28:56 +0100 Subject: [PATCH 1/5] - move project to baseline standard openapi-generator with version 7.20.0 to allow future updates --- boat-engine/pom.xml | 2 +- boat-maven-plugin/pom.xml | 4 +- boat-quay/boat-quay-lint/pom.xml | 2 +- boat-quay/boat-quay-rules/pom.xml | 2 +- boat-quay/pom.xml | 2 +- boat-scaffold/pom.xml | 4 +- .../templates/boat-java/ApiClient.mustache | 54 +- .../BeanValidationException.mustache | 15 +- .../boat-java/Configuration.mustache | 65 +- .../CustomInstantDeserializer.mustache | 2 + .../main/templates/boat-java/JSON.mustache | 4 +- .../boat-java/JavaTimeFormatter.mustache | 75 +- .../main/templates/boat-java/Pair.mustache | 55 +- .../main/templates/boat-java/README.mustache | 25 +- .../boat-java/RFC3339DateFormat.mustache | 4 +- .../RFC3339InstantDeserializer.mustache | 90 ++ .../boat-java/RFC3339JavaTimeModule.mustache | 29 + .../boat-java/ServerConfiguration.mustache | 6 +- .../boat-java/ServerVariable.mustache | 4 + .../templates/boat-java/StringUtil.mustache | 23 + .../additionalModelTypeAnnotations.mustache | 2 +- .../boat-java/additional_properties.mustache | 34 + .../src/main/templates/boat-java/api.mustache | 24 +- .../templates/boat-java/apiException.mustache | 5 +- .../main/templates/boat-java/api_doc.mustache | 2 +- .../templates/boat-java/api_test.mustache | 14 +- .../boat-java/auth/ApiKeyAuth.mustache | 1 + .../boat-java/auth/Authentication.mustache | 18 +- .../boat-java/auth/HttpBasicAuth.mustache | 3 +- .../boat-java/auth/HttpBearerAuth.mustache | 29 +- .../templates/boat-java/auth/OAuth.mustache | 1 + .../boat-java/auth/OAuthFlow.mustache | 3 +- .../templates/boat-java/build.gradle.mustache | 50 +- .../boat-java/generatedAnnotation.mustache | 2 +- .../templates/boat-java/git_push.sh.mustache | 2 +- .../templates/boat-java/gradle-wrapper.jar | Bin 59536 -> 43453 bytes .../gradle-wrapper.properties.mustache | 6 +- .../boat-java/gradle.properties.mustache | 2 +- .../templates/boat-java/gradlew.bat.mustache | 39 +- .../main/templates/boat-java/gradlew.mustache | 191 ++-- .../boat-java/jackson_annotations.mustache | 19 +- .../templates/boat-java/javaBuilder.mustache | 82 ++ .../apache-httpclient/ApiClient.mustache | 201 ++-- .../apache-httpclient/BaseApi.mustache | 111 +++ .../apache-httpclient/README.mustache | 23 +- .../libraries/apache-httpclient/api.mustache | 131 ++- .../apache-httpclient/api_test.mustache | 15 +- .../apache-httpclient/build.gradle.mustache | 21 +- .../libraries/apache-httpclient/pom.mustache | 131 +-- .../libraries/feign/ApiClient.mustache | 105 +- .../feign/ApiResponseDecoder.mustache | 85 +- .../libraries/feign/EncodingUtils.mustache | 2 + .../libraries/feign/ParamExpander.mustache | 2 + .../boat-java/libraries/feign/README.mustache | 2 +- .../feign/additional_properties.mustache | 45 + .../boat-java/libraries/feign/api.mustache | 34 +- .../libraries/feign/api_test.mustache | 7 +- .../feign/auth/ApiErrorDecoder.mustache | 6 +- .../libraries/feign/auth/ApiKeyAuth.mustache | 4 +- .../feign/auth/DefaultApi20Impl.mustache | 3 + .../feign/auth/HttpBasicAuth.mustache | 2 + .../feign/auth/HttpBearerAuth.mustache | 26 +- .../libraries/feign/auth/OAuth.mustache | 3 + .../auth/OauthClientCredentialsGrant.mustache | 3 + .../feign/auth/OauthPasswordGrant.mustache | 3 + .../libraries/feign/build.gradle.mustache | 45 +- .../libraries/feign/build.sbt.mustache | 42 +- .../boat-java/libraries/feign/model.mustache | 78 ++ .../feign/model/ApiResponse.mustache | 12 +- .../libraries/feign/model_test.mustache | 7 - .../boat-java/libraries/feign/pojo.mustache | 580 +++++++++++ .../boat-java/libraries/feign/pom.mustache | 101 +- .../google-api-client/ApiClient.mustache | 20 +- .../libraries/google-api-client/api.mustache | 45 +- .../google-api-client/api_test.mustache | 17 +- .../google-api-client/build.gradle.mustache | 12 +- .../google-api-client/build.sbt.mustache | 6 +- .../libraries/google-api-client/pom.mustache | 40 +- .../jersey2/AbstractOpenApiSchema.mustache | 8 +- .../libraries/jersey2/ApiClient.mustache | 403 ++++---- .../boat-java/libraries/jersey2/JSON.mustache | 59 +- .../jersey2/additional_properties.mustache | 2 +- .../libraries/jersey2/anyof_model.mustache | 61 +- .../boat-java/libraries/jersey2/api.mustache | 130 ++- .../libraries/jersey2/apiException.mustache | 3 + .../libraries/jersey2/api_test.mustache | 7 +- .../jersey2/auth/ApiKeyAuth.mustache | 1 + .../jersey2/auth/Authentication.mustache | 2 + .../jersey2/auth/HttpBasicAuth.mustache | 1 + .../jersey2/auth/HttpBearerAuth.mustache | 1 + .../libraries/jersey2/auth/OAuth.mustache | 3 +- .../libraries/jersey2/build.gradle.mustache | 36 +- .../libraries/jersey2/build.sbt.mustache | 17 +- .../libraries/jersey2/model.mustache | 7 +- .../libraries/jersey2/model_doc.mustache | 4 + .../libraries/jersey2/model_test.mustache | 7 - .../libraries/jersey2/oneof_model.mustache | 120 ++- .../boat-java/libraries/jersey2/pojo.mustache | 117 ++- .../boat-java/libraries/jersey2/pom.mustache | 50 +- .../jersey3/AbstractOpenApiSchema.mustache | 8 +- .../libraries/jersey3/ApiClient.mustache | 396 ++++---- .../boat-java/libraries/jersey3/JSON.mustache | 59 +- .../jersey3/additional_properties.mustache | 2 +- .../libraries/jersey3/anyof_model.mustache | 61 +- .../boat-java/libraries/jersey3/api.mustache | 130 ++- .../libraries/jersey3/apiException.mustache | 3 + .../libraries/jersey3/api_test.mustache | 7 +- .../jersey3/auth/ApiKeyAuth.mustache | 1 + .../jersey3/auth/Authentication.mustache | 2 + .../jersey3/auth/HttpBasicAuth.mustache | 1 + .../jersey3/auth/HttpBearerAuth.mustache | 1 + .../libraries/jersey3/auth/OAuth.mustache | 3 +- .../libraries/jersey3/build.gradle.mustache | 24 +- .../libraries/jersey3/build.sbt.mustache | 17 +- .../jersey3/generatedAnnotation.mustache | 2 +- .../libraries/jersey3/model.mustache | 7 +- .../libraries/jersey3/model_doc.mustache | 4 + .../libraries/jersey3/model_test.mustache | 7 - .../libraries/jersey3/oneof_model.mustache | 120 ++- .../boat-java/libraries/jersey3/pojo.mustache | 119 ++- .../boat-java/libraries/jersey3/pom.mustache | 52 +- .../libraries/microprofile/README.mustache | 11 +- .../additional_properties.mustache | 45 + .../libraries/microprofile/api.mustache | 68 +- .../microprofile/api_exception.mustache | 2 +- .../api_exception_mapper.mustache | 4 + .../libraries/microprofile/api_test.mustache | 27 +- .../beanValidationCookieParams.mustache | 1 + .../microprofile/beanValidationCore.mustache | 4 +- .../microprofile/client_operation.mustache | 1 + .../microprofile/cookieParams.mustache | 1 + .../microprofile/cookieParamsImpl.mustache | 1 + .../libraries/microprofile/enumClass.mustache | 24 +- .../microprofile/enumOuterClass.mustache | 30 +- .../microprofile/formParams.mustache | 2 +- .../microprofile/formParamsImpl.mustache | 2 +- .../formParamsNameSuffix.mustache | 1 + .../microprofile/generatedAnnotation.mustache | 2 +- .../microprofile/kumuluzee.pom.mustache | 6 +- .../microprofile/licenseInfo.mustache | 2 +- .../libraries/microprofile/model.mustache | 44 +- .../libraries/microprofile/pojo.mustache | 121 +-- .../microprofile/pojoOverrides.mustache | 70 ++ .../libraries/microprofile/pom.mustache | 88 +- .../libraries/microprofile/pom_3.0.mustache | 90 +- .../microprofile/server_operation.mustache | 1 + .../native/AbstractOpenApiSchema.mustache | 4 +- .../libraries/native/ApiClient.mustache | 215 +++- .../libraries/native/ApiResponse.mustache | 2 + .../boat-java/libraries/native/JSON.mustache | 31 +- .../libraries/native/README.mustache | 23 +- .../libraries/native/anyof_model.mustache | 172 +++- .../boat-java/libraries/native/api.mustache | 673 +++++++++++-- .../libraries/native/apiException.mustache | 3 + .../libraries/native/api_doc.mustache | 2 +- .../libraries/native/api_test.mustache | 13 +- .../libraries/native/build.gradle.mustache | 67 +- .../native/generatedAnnotation.mustache | 2 +- .../boat-java/libraries/native/model.mustache | 19 +- .../libraries/native/modelEnum.mustache | 123 +++ .../libraries/native/model_doc.mustache | 4 + .../libraries/native/oneof_model.mustache | 172 +++- .../boat-java/libraries/native/pojo.mustache | 282 ++++-- .../boat-java/libraries/native/pom.mustache | 100 +- .../AbstractOpenApiSchema.mustache | 9 +- .../libraries/okhttp-gson/ApiClient.mustache | 367 +++++-- .../libraries/okhttp-gson/JSON.mustache | 29 +- .../libraries/okhttp-gson/README.mustache | 24 +- .../okhttp-gson/anyof_model.mustache | 346 +++++-- .../libraries/okhttp-gson/api.mustache | 115 ++- .../okhttp-gson/apiException.mustache | 8 +- .../libraries/okhttp-gson/api_doc.mustache | 6 +- .../libraries/okhttp-gson/api_test.mustache | 7 +- .../okhttp-gson/auth/AWS4Auth.mustache | 105 ++ .../okhttp-gson/auth/ApiKeyAuth.mustache | 1 + .../okhttp-gson/auth/Authentication.mustache | 2 + .../okhttp-gson/auth/HttpBasicAuth.mustache | 2 - .../okhttp-gson/auth/HttpBearerAuth.mustache | 21 +- .../libraries/okhttp-gson/auth/OAuth.mustache | 1 + .../auth/OAuthOkHttpClient.mustache | 2 + .../okhttp-gson/auth/RetryingOAuth.mustache | 2 + .../okhttp-gson/build.gradle.mustache | 45 +- .../libraries/okhttp-gson/build.sbt.mustache | 24 +- .../libraries/okhttp-gson/model.mustache | 32 +- .../libraries/okhttp-gson/modelEnum.mustache | 88 ++ .../okhttp-gson/modelInnerEnum.mustache | 66 ++ .../libraries/okhttp-gson/model_test.mustache | 7 - .../okhttp-gson/oneof_model.mustache | 414 ++++++-- .../libraries/okhttp-gson/pojo.mustache | 379 ++++--- .../libraries/okhttp-gson/pom.mustache | 85 +- .../libraries/rest-assured/ApiClient.mustache | 9 +- .../rest-assured/JacksonObjectMapper.mustache | 2 +- .../libraries/rest-assured/api.mustache | 41 +- .../libraries/rest-assured/api_doc.mustache | 2 +- .../libraries/rest-assured/api_test.mustache | 18 +- .../rest-assured/build.gradle.mustache | 40 +- .../libraries/rest-assured/build.sbt.mustache | 26 +- .../libraries/rest-assured/pom.mustache | 50 +- .../libraries/restclient/ApiClient.mustache | 902 +++++++++++++++++ .../libraries/restclient/README.mustache | 216 ++++ .../restclient/additional_properties.mustache | 45 + .../libraries/restclient/api.mustache | 221 ++++ .../libraries/restclient/api_test.mustache | 45 + .../restclient/auth/ApiKeyAuth.mustache | 65 ++ .../restclient/auth/Authentication.mustache | 19 + .../restclient/auth/HttpBasicAuth.mustache | 41 + .../restclient/auth/HttpBearerAuth.mustache | 59 ++ .../libraries/restclient/auth/OAuth.mustache | 51 + .../restclient/auth/OAuthFlow.mustache | 7 + .../restclient/build.gradle.mustache | 159 +++ .../libraries/restclient/model.mustache | 78 ++ .../libraries/restclient/pojo.mustache | 630 ++++++++++++ .../libraries/restclient/pom.mustache | 365 +++++++ .../single_request_parameter.mustache | 110 ++ .../libraries/resteasy/ApiClient.mustache | 65 +- .../libraries/resteasy/JSON.mustache | 7 +- .../boat-java/libraries/resteasy/api.mustache | 15 +- .../libraries/resteasy/build.gradle.mustache | 18 +- .../libraries/resteasy/build.sbt.mustache | 19 +- .../boat-java/libraries/resteasy/pom.mustache | 43 +- .../libraries/resttemplate/ApiClient.mustache | 187 +++- .../libraries/resttemplate/BaseApi.mustache | 77 ++ .../additional_properties.mustache | 45 + .../libraries/resttemplate/api.mustache | 74 +- .../libraries/resttemplate/api_test.mustache | 23 +- .../resttemplate/auth/ApiKeyAuth.mustache | 3 + .../resttemplate/auth/Authentication.mustache | 7 +- .../resttemplate/auth/HttpBasicAuth.mustache | 3 + .../resttemplate/auth/HttpBearerAuth.mustache | 31 +- .../resttemplate/auth/OAuth.mustache | 39 +- .../resttemplate/auth/OAuthFlow.mustache | 2 + .../resttemplate/build.gradle.mustache | 76 +- .../libraries/resttemplate/model.mustache | 78 ++ .../libraries/resttemplate/pojo.mustache | 630 ++++++++++++ .../libraries/resttemplate/pom.mustache | 112 ++- .../libraries/retrofit2/ApiClient.mustache | 57 +- .../retrofit2/CollectionFormats.mustache | 2 + .../libraries/retrofit2/JSON.mustache | 2 + .../libraries/retrofit2/JSON_jackson.mustache | 264 +++++ .../libraries/retrofit2/api.mustache | 7 +- .../libraries/retrofit2/api_test.mustache | 14 +- .../retrofit2/auth/ApiKeyAuth.mustache | 4 +- .../retrofit2/auth/HttpBasicAuth.mustache | 2 + .../retrofit2/auth/HttpBearerAuth.mustache | 2 + .../libraries/retrofit2/auth/OAuth.mustache | 2 + .../retrofit2/auth/OAuthOkHttpClient.mustache | 2 + .../libraries/retrofit2/build.gradle.mustache | 36 +- .../libraries/retrofit2/build.sbt.mustache | 30 +- .../play-common/auth/ApiKeyAuth.mustache | 1 + .../retrofit2/play24/ApiClient.mustache | 12 +- .../play24/Play24CallAdapterFactory.mustache | 2 + .../play24/Play24CallFactory.mustache | 10 +- .../libraries/retrofit2/play24/api.mustache | 2 - .../retrofit2/play25/ApiClient.mustache | 12 +- .../play25/Play25CallAdapterFactory.mustache | 2 + .../play25/Play25CallFactory.mustache | 10 +- .../libraries/retrofit2/play25/api.mustache | 2 - .../retrofit2/play26/ApiClient.mustache | 18 +- .../play26/Play26CallAdapterFactory.mustache | 2 + .../play26/Play26CallFactory.mustache | 8 +- .../libraries/retrofit2/play26/api.mustache | 7 +- .../libraries/retrofit2/pom.mustache | 63 +- .../libraries/vertx/ApiClient.mustache | 77 +- .../libraries/vertx/Configuration.mustache | 3 + .../boat-java/libraries/vertx/api.mustache | 30 +- .../libraries/vertx/apiException.mustache | 3 + .../libraries/vertx/apiImpl.mustache | 3 +- .../libraries/vertx/api_test.mustache | 30 +- .../libraries/vertx/auth/ApiKeyAuth.mustache | 1 + .../vertx/auth/Authentication.mustache | 2 + .../vertx/auth/HttpBasicAuth.mustache | 1 + .../vertx/auth/HttpBearerAuth.mustache | 1 + .../libraries/vertx/auth/OAuth.mustache | 1 + .../libraries/vertx/build.gradle.mustache | 12 +- .../boat-java/libraries/vertx/pom.mustache | 35 +- .../libraries/vertx/rxApiImpl.mustache | 1 + .../libraries/webclient/ApiClient.mustache | 103 +- .../webclient/additional_properties.mustache | 45 + .../libraries/webclient/api.mustache | 76 +- .../libraries/webclient/api_test.mustache | 19 +- .../webclient/auth/ApiKeyAuth.mustache | 3 + .../webclient/auth/Authentication.mustache | 7 +- .../webclient/auth/HttpBasicAuth.mustache | 3 + .../webclient/auth/HttpBearerAuth.mustache | 3 + .../libraries/webclient/auth/OAuth.mustache | 3 + .../webclient/auth/OAuthFlow.mustache | 2 + .../libraries/webclient/build.gradle.mustache | 66 +- .../libraries/webclient/model.mustache | 78 ++ .../libraries/webclient/pojo.mustache | 630 ++++++++++++ .../libraries/webclient/pom.mustache | 78 +- .../single_request_parameter.mustache | 106 ++ .../templates/boat-java/licenseInfo.mustache | 8 +- .../templates/boat-java/maven.yml.mustache | 6 +- .../main/templates/boat-java/model.mustache | 53 +- .../templates/boat-java/modelEnum.mustache | 39 +- .../boat-java/modelInnerEnum.mustache | 14 +- .../templates/boat-java/model_test.mustache | 19 +- .../nullable_var_annotations.mustache | 1 + .../boat-java/oneof_interface.mustache | 5 +- .../main/templates/boat-java/permits.mustache | 1 + .../main/templates/boat-java/pojo.mustache | 942 +++++++++--------- .../templates/boat-java/pojo_doc.mustache | 2 +- .../src/main/templates/boat-java/pom.mustache | 108 +- .../main/templates/boat-java/sealed.mustache | 1 + .../main/templates/boat-java/travis.mustache | 2 +- .../boat-java/typeInfoAnnotation.mustache | 14 +- .../additionalModelTypeAnnotations.mustache | 3 +- .../boat-spring/additionalProperties.mustache | 39 + .../main/templates/boat-spring/api.mustache | 169 ++-- .../boat-spring/apiController.mustache | 44 +- .../boat-spring/apiDelegate.mustache | 54 +- .../boat-spring/apiException.mustache | 10 +- .../boat-spring/apiOriginFilter.mustache | 18 +- .../boat-spring/apiResponseMessage.mustache | 14 +- .../templates/boat-spring/apiUtil.mustache | 17 +- .../boat-spring/beanValidation.mustache | 2 +- .../boat-spring/beanValidationCore.mustache | 29 +- .../beanValidationPathParams.mustache | 2 +- .../templates/boat-spring/bodyParams.mustache | 2 +- .../templates/boat-spring/converter.mustache | 21 +- .../boat-spring/cookieParams.mustache | 2 +- .../templates/boat-spring/enumClass.mustache | 18 +- .../boat-spring/enumOuterClass.mustache | 20 +- .../templates/boat-spring/formParams.mustache | 2 +- .../boat-spring/generatedAnnotation.mustache | 2 +- .../boat-spring/headerParams.mustache | 2 +- .../boat-spring/homeController.mustache | 2 +- .../boat-spring/javaBuilder.mustache | 93 ++ .../spring-boot/RFC3339DateFormat.mustache | 2 +- .../spring-boot/application.mustache | 5 + .../spring-boot/openapi2SpringBoot.mustache | 13 +- .../libraries/spring-boot/pom-sb3.mustache | 78 +- .../libraries/spring-boot/pom-sb4.mustache | 251 +++++ .../libraries/spring-boot/pom.mustache | 95 +- .../libraries/spring-cloud/apiClient.mustache | 2 +- .../spring-cloud/clientConfiguration.mustache | 152 ++- .../clientPropertiesConfiguration.mustache | 43 + .../libraries/spring-cloud/pom-sb3.mustache | 51 +- .../libraries/spring-cloud/pom-sb4.mustache | 167 ++++ .../libraries/spring-cloud/pom.mustache | 42 +- .../spring-http-interface/README.mustache | 21 + .../spring-http-interface/api.mustache | 76 ++ .../httpInterfacesConfiguration.mustache | 49 + .../spring-http-interface/pom-sb4.mustache | 107 ++ .../spring-http-interface/pom.mustache | 105 ++ .../boat-spring/lombokAnnotation.mustache | 39 + .../templates/boat-spring/methodBody.mustache | 26 +- .../main/templates/boat-spring/model.mustache | 52 +- .../boat-spring/notFoundException.mustache | 8 +- .../boat-spring/nullableAnnotation.mustache | 1 + .../boat-spring/nullableDataType.mustache | 2 +- .../nullableDataTypeBeanValidation.mustache | 1 + .../boat-spring/oneof_interface.mustache | 14 +- .../openapiDocumentationConfig.mustache | 15 +- .../boat-spring/optionalDataType.mustache | 14 +- .../templates/boat-spring/paramDoc.mustache | 2 +- .../templates/boat-spring/pathParams.mustache | 2 +- .../templates/boat-spring/permits.mustache | 1 + .../main/templates/boat-spring/pojo.mustache | 494 +++++---- .../boat-spring/project/build.properties | 2 +- .../boat-spring/queryParams.mustache | 2 +- .../boat-spring/responseType.mustache | 2 +- .../boat-spring/returnTypes.mustache | 2 +- .../templates/boat-spring/sealed.mustache | 1 + .../springdocDocumentationConfig.mustache | 2 +- .../boat-spring/xmlAnnotation.mustache | 7 +- boat-trail-resources/pom.xml | 2 +- pom.xml | 4 +- tests/pom.xml | 2 +- 369 files changed, 15915 insertions(+), 4346 deletions(-) create mode 100644 boat-scaffold/src/main/templates/boat-java/RFC3339InstantDeserializer.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/RFC3339JavaTimeModule.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/additional_properties.mustache mode change 100755 => 100644 boat-scaffold/src/main/templates/boat-java/git_push.sh.mustache mode change 100755 => 100644 boat-scaffold/src/main/templates/boat-java/gradlew.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/javaBuilder.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/BaseApi.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/feign/additional_properties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/feign/model.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/feign/pojo.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/additional_properties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCookieParams.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/client_operation.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParams.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParamsImpl.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsNameSuffix.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojoOverrides.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/microprofile/server_operation.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/native/modelEnum.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/AWS4Auth.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelEnum.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelInnerEnum.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/ApiClient.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/README.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/additional_properties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/api.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/api_test.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/ApiKeyAuth.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/Authentication.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBasicAuth.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBearerAuth.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuth.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuthFlow.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/build.gradle.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/model.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/pojo.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/pom.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/restclient/single_request_parameter.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/BaseApi.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/additional_properties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/model.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pojo.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON_jackson.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/webclient/additional_properties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/webclient/model.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/webclient/pojo.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/libraries/webclient/single_request_parameter.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/nullable_var_annotations.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/permits.mustache create mode 100644 boat-scaffold/src/main/templates/boat-java/sealed.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/additionalProperties.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/javaBuilder.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb4.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientPropertiesConfiguration.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb4.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/README.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/api.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/httpInterfacesConfiguration.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom-sb4.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/lombokAnnotation.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/permits.mustache create mode 100644 boat-scaffold/src/main/templates/boat-spring/sealed.mustache diff --git a/boat-engine/pom.xml b/boat-engine/pom.xml index b3793d829..776bed88a 100644 --- a/boat-engine/pom.xml +++ b/boat-engine/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-engine jar diff --git a/boat-maven-plugin/pom.xml b/boat-maven-plugin/pom.xml index 7286b5300..d60c02d6f 100644 --- a/boat-maven-plugin/pom.xml +++ b/boat-maven-plugin/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-maven-plugin @@ -360,7 +360,7 @@ com.backbase.oss boat-maven-plugin - 0.17.50 + 0.18.0 diff --git a/boat-quay/boat-quay-lint/pom.xml b/boat-quay/boat-quay-lint/pom.xml index c20e60663..14df40cd7 100644 --- a/boat-quay/boat-quay-lint/pom.xml +++ b/boat-quay/boat-quay-lint/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss boat-quay - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-quay-lint diff --git a/boat-quay/boat-quay-rules/pom.xml b/boat-quay/boat-quay-rules/pom.xml index 2adaed33a..0f48c13c5 100644 --- a/boat-quay/boat-quay-rules/pom.xml +++ b/boat-quay/boat-quay-rules/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss boat-quay - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-quay-rules diff --git a/boat-quay/pom.xml b/boat-quay/pom.xml index 26415dbb1..7a2d7f695 100644 --- a/boat-quay/pom.xml +++ b/boat-quay/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT diff --git a/boat-scaffold/pom.xml b/boat-scaffold/pom.xml index d62bb8924..e40ef908f 100644 --- a/boat-scaffold/pom.xml +++ b/boat-scaffold/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-scaffold @@ -102,7 +102,7 @@ com.backbase.oss boat-trail-resources - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT test diff --git a/boat-scaffold/src/main/templates/boat-java/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/ApiClient.mustache index 69494573c..c38f59dcb 100644 --- a/boat-scaffold/src/main/templates/boat-java/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/ApiClient.mustache @@ -8,7 +8,12 @@ import com.fasterxml.jackson.datatype.joda.JodaModule; {{/joda}} import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.time.OffsetDateTime; +{{#useJakartaEe}} +import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider; +{{/useJakartaEe}} +{{^useJakartaEe}} import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; +{{/useJakartaEe}} import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; @@ -21,9 +26,9 @@ import com.sun.jersey.api.client.WebResource.Builder; import com.sun.jersey.multipart.FormDataMultiPart; import com.sun.jersey.multipart.file.FileDataBodyPart; -import javax.ws.rs.core.Cookie; -import javax.ws.rs.core.Response.Status.Family; -import javax.ws.rs.core.MediaType; +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; @@ -59,10 +64,11 @@ 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 Map defaultHeaderMap = new HashMap(); + protected Map defaultCookieMap = new HashMap(); + protected String basePath = "{{{basePath}}}"; protected List servers = new ArrayList({{#servers}}{{#-first}}Arrays.asList( {{/-first}} new ServerConfiguration( "{{{url}}}", @@ -90,18 +96,18 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { ){{/-last}}{{/servers}}); protected Integer serverIndex = 0; protected Map serverVariables = null; - private boolean debugging = false; - private int connectionTimeout = 0; + protected boolean debugging = false; + protected int connectionTimeout = 0; - private Client httpClient; - private ObjectMapper objectMapper; + protected Client httpClient; + protected ObjectMapper objectMapper; - private Map authentications; + protected Map authentications; - private int statusCode; - private Map> responseHeaders; + protected int statusCode; + protected Map> responseHeaders; - private DateFormat dateFormat; + protected DateFormat dateFormat; public ApiClient() { objectMapper = new ObjectMapper(); @@ -124,8 +130,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + 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. @@ -192,6 +198,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { public ApiClient setBasePath(String basePath) { this.basePath = basePath; + this.serverIndex = null; return this; } @@ -528,11 +535,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param value The value of the parameter. * @return A list of {@code Pair} objects. */ - public List parameterToPairs(String collectionFormat, String name, Collection value) { + public List parameterToPairs(String collectionFormat, String name, Collection value) { List params = new ArrayList(); // preconditions - if (name == null || name.isEmpty() || value == null) { + if (name == null || name.isEmpty() || value == null || value.isEmpty()) { return params; } @@ -682,11 +689,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param collectionQueryParams The collection query parameters * @return The full URL */ - private String buildUrl(String path, List queryParams, List collectionQueryParams) { + protected String buildUrl(String path, List queryParams, List collectionQueryParams) { String baseURL; if (serverIndex != null) { if (serverIndex < 0 || serverIndex >= servers.size()) { throw new ArrayIndexOutOfBoundsException(String.format( + java.util.Locale.ROOT, "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() )); } @@ -735,7 +743,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { 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 { + protected 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"); } @@ -848,7 +856,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param headerParams Header parameters * @param cookieParams Cookie parameters */ - private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { + protected 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); @@ -861,7 +869,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param formParams Form parameters * @return HTTP form encoded parameters */ - private String getXWWWFormUrlencodedParams(Map formParams) { + protected String getXWWWFormUrlencodedParams(Map formParams) { StringBuilder formParamBuilder = new StringBuilder(); for (Entry param : formParams.entrySet()) { @@ -883,4 +891,4 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return encodedFormParams; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/BeanValidationException.mustache b/boat-scaffold/src/main/templates/boat-java/BeanValidationException.mustache index 179abe2fa..d551902f8 100644 --- a/boat-scaffold/src/main/templates/boat-java/BeanValidationException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/BeanValidationException.mustache @@ -1,15 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.Set; -{{^useJakartaEe}} -import javax.validation.ConstraintViolation; -import javax.validation.ValidationException; -{{/useJakartaEe}} -{{#useJakartaEe}} -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ValidationException; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.ConstraintViolation; +import {{javaxPackage}}.validation.ValidationException; + public class BeanValidationException extends ValidationException { /** * @@ -29,4 +26,4 @@ public class BeanValidationException extends ValidationException { this.violations = violations; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/Configuration.mustache b/boat-scaffold/src/main/templates/boat-java/Configuration.mustache index cb425df35..c932ddf99 100644 --- a/boat-scaffold/src/main/templates/boat-java/Configuration.mustache +++ b/boat-scaffold/src/main/templates/boat-java/Configuration.mustache @@ -2,27 +2,52 @@ package {{invokerPackage}}; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + {{>generatedAnnotation}} + public class Configuration { - 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; - } + public static final String VERSION = "{{{artifactVersion}}}"; + + private static final AtomicReference defaultApiClient = new AtomicReference<>(); + private static volatile Supplier apiClientFactory = ApiClient::new; - /** - * 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; + /** + * 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() { + ApiClient client = defaultApiClient.get(); + if (client == null) { + client = defaultApiClient.updateAndGet(val -> { + if (val != null) { // changed by another thread + return val; + } + return apiClientFactory.get(); + }); } -} + return client; + } + + /** + * 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.set(apiClient); + } + + /** + * set the callback used to create new ApiClient objects + */ + public static void setApiClientFactory(Supplier factory) { + apiClientFactory = Objects.requireNonNull(factory); + } + + private Configuration() { + } +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/CustomInstantDeserializer.mustache b/boat-scaffold/src/main/templates/boat-java/CustomInstantDeserializer.mustache index 5ebea810e..d4a3ca97e 100644 --- a/boat-scaffold/src/main/templates/boat-java/CustomInstantDeserializer.mustache +++ b/boat-scaffold/src/main/templates/boat-java/CustomInstantDeserializer.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.core.JsonParser; diff --git a/boat-scaffold/src/main/templates/boat-java/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/JSON.mustache index d315850bb..5ef02660d 100644 --- a/boat-scaffold/src/main/templates/boat-java/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/JSON.mustache @@ -31,9 +31,11 @@ import java.lang.reflect.Type; import java.text.DateFormat; import java.text.ParseException; import java.text.ParsePosition; +{{#jsr310}} import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +{{/jsr310}} import java.util.Date; import java.util.Locale; import java.util.Map; @@ -536,4 +538,4 @@ public class JSON { return this; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/JavaTimeFormatter.mustache b/boat-scaffold/src/main/templates/boat-java/JavaTimeFormatter.mustache index f3fb34e55..e96325c40 100644 --- a/boat-scaffold/src/main/templates/boat-java/JavaTimeFormatter.mustache +++ b/boat-scaffold/src/main/templates/boat-java/JavaTimeFormatter.mustache @@ -10,44 +10,49 @@ import java.time.format.DateTimeParseException; * 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; - 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; + } - /** - * 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; + } - /** - * 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); } + } - /** - * 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); - } -} + /** + * 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); + } +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/Pair.mustache b/boat-scaffold/src/main/templates/boat-java/Pair.mustache index ff9e06b20..fc7d0568d 100644 --- a/boat-scaffold/src/main/templates/boat-java/Pair.mustache +++ b/boat-scaffold/src/main/templates/boat-java/Pair.mustache @@ -3,48 +3,25 @@ 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 class Pair { + private final String name; + private final String value; - public String getValue() { - return this.value; - } + public Pair(String name, String value) { + this.name = isValidString(name) ? name : ""; + this.value = isValidString(value) ? value : ""; + } - private boolean isValidString(String arg) { - if (arg == null) { - return false; - } + public String getName() { + return this.name; + } - if (arg.trim().isEmpty()) { - return false; - } + public String getValue() { + return this.value; + } - return true; - } + private static boolean isValidString(String arg) { + return arg != null; + } } diff --git a/boat-scaffold/src/main/templates/boat-java/README.mustache b/boat-scaffold/src/main/templates/boat-java/README.mustache index 777e2c860..157c8b6e0 100644 --- a/boat-scaffold/src/main/templates/boat-java/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/README.mustache @@ -8,6 +8,8 @@ - Build date: {{generatedDate}} {{/hideGenerationTimestamp}} +- Generator version: {{generatorVersion}} + {{{appDescriptionWithNewLines}}} {{#infoUrl}} @@ -197,11 +199,14 @@ Class | Method | HTTP request | Description {{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) {{/model}}{{/models}} + ## Documentation for Authorization -{{^authMethods}}All endpoints do not require authorization. -{{/authMethods}}Authentication schemes defined for the API: -{{#authMethods}}### {{name}} +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{name}} {{#isApiKey}} @@ -209,10 +214,18 @@ Class | Method | HTTP request | Description - **API key parameter name**: {{keyParamName}} - **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} {{/isApiKey}} -{{#isBasic}} +{{#isBasicBasic}} - **Type**: HTTP basic authentication -{{/isBasic}} +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} {{#isOAuth}} - **Type**: OAuth @@ -232,4 +245,4 @@ It's recommended to create an instance of `ApiClient` per thread in a multithrea ## Author {{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} -{{/-last}}{{/apis}}{{/apiInfo}} \ No newline at end of file +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/boat-scaffold/src/main/templates/boat-java/RFC3339DateFormat.mustache b/boat-scaffold/src/main/templates/boat-java/RFC3339DateFormat.mustache index 311616a4e..c2c205c20 100644 --- a/boat-scaffold/src/main/templates/boat-java/RFC3339DateFormat.mustache +++ b/boat-scaffold/src/main/templates/boat-java/RFC3339DateFormat.mustache @@ -11,6 +11,8 @@ import java.text.DecimalFormat; import java.util.GregorianCalendar; import java.util.TimeZone; +{{>generatedAnnotation}} + public class RFC3339DateFormat extends DateFormat { private static final long serialVersionUID = 1L; private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC"); @@ -43,4 +45,4 @@ public class RFC3339DateFormat extends DateFormat { public Object clone() { return super.clone(); } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/RFC3339InstantDeserializer.mustache b/boat-scaffold/src/main/templates/boat-java/RFC3339InstantDeserializer.mustache new file mode 100644 index 000000000..ea4b0799a --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/RFC3339InstantDeserializer.mustache @@ -0,0 +1,90 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature; +import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer; + +{{>generatedAnnotation}} + +public class RFC3339InstantDeserializer extends InstantDeserializer { + private static final long serialVersionUID = 1L; + private final static boolean DEFAULT_NORMALIZE_ZONE_ID = JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID.enabledByDefault(); + private final static boolean DEFAULT_ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS + = JavaTimeFeature.ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS.enabledByDefault(); + + public static final RFC3339InstantDeserializer INSTANT = new RFC3339InstantDeserializer<>( + Instant.class, DateTimeFormatter.ISO_INSTANT, + Instant::from, + a -> Instant.ofEpochMilli( a.value ), + a -> Instant.ofEpochSecond( a.integer, a.fraction ), + null, + true, // yes, replace zero offset with Z + DEFAULT_NORMALIZE_ZONE_ID, + DEFAULT_ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS + ); + + public static final RFC3339InstantDeserializer OFFSET_DATE_TIME = new RFC3339InstantDeserializer<>( + OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, + OffsetDateTime::from, + a -> OffsetDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ), + a -> OffsetDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ), + (d, z) -> ( d.isEqual( OffsetDateTime.MIN ) || d.isEqual( OffsetDateTime.MAX ) ? + d : + d.withOffsetSameInstant( z.getRules().getOffset( d.toLocalDateTime() ) ) ), + true, // yes, replace zero offset with Z + DEFAULT_NORMALIZE_ZONE_ID, + DEFAULT_ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS + ); + + public static final RFC3339InstantDeserializer ZONED_DATE_TIME = new RFC3339InstantDeserializer<>( + ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, + ZonedDateTime::from, + a -> ZonedDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ), + a -> ZonedDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ), + ZonedDateTime::withZoneSameInstant, + false, // keep zero offset and Z separate since zones explicitly supported + DEFAULT_NORMALIZE_ZONE_ID, + DEFAULT_ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS + ); + + protected RFC3339InstantDeserializer( + Class supportedType, + DateTimeFormatter formatter, + Function parsedToValue, + Function fromMilliseconds, + Function fromNanoseconds, + BiFunction adjust, + boolean replaceZeroOffsetAsZ, + boolean normalizeZoneId, + boolean readNumericStringsAsTimestamp) { + super( + supportedType, + formatter, + parsedToValue, + fromMilliseconds, + fromNanoseconds, + adjust, + replaceZeroOffsetAsZ, + normalizeZoneId, + readNumericStringsAsTimestamp + ); + } + + @Override + protected T _fromString(JsonParser p, DeserializationContext ctxt, String string0) throws IOException { + return super._fromString(p, ctxt, string0.replace( ' ', 'T' )); + } +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/RFC3339JavaTimeModule.mustache b/boat-scaffold/src/main/templates/boat-java/RFC3339JavaTimeModule.mustache new file mode 100644 index 000000000..6b780cd43 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/RFC3339JavaTimeModule.mustache @@ -0,0 +1,29 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.Module.SetupContext; + +{{>generatedAnnotation}} + +public class RFC3339JavaTimeModule extends SimpleModule { + private static final long serialVersionUID = 1L; + + public RFC3339JavaTimeModule() { + super("RFC3339JavaTimeModule"); + } + + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + + addDeserializer(Instant.class, RFC3339InstantDeserializer.INSTANT); + addDeserializer(OffsetDateTime.class, RFC3339InstantDeserializer.OFFSET_DATE_TIME); + addDeserializer(ZonedDateTime.class, RFC3339InstantDeserializer.ZONED_DATE_TIME); + } + +} diff --git a/boat-scaffold/src/main/templates/boat-java/ServerConfiguration.mustache b/boat-scaffold/src/main/templates/boat-java/ServerConfiguration.mustache index e324da7a8..4c94d0bd0 100644 --- a/boat-scaffold/src/main/templates/boat-java/ServerConfiguration.mustache +++ b/boat-scaffold/src/main/templates/boat-java/ServerConfiguration.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.Map; @@ -5,6 +7,8 @@ import java.util.Map; /** * Representing a Server configuration. */ +{{>generatedAnnotation}} + public class ServerConfiguration { public String URL; public String description; @@ -55,4 +59,4 @@ public class ServerConfiguration { public String URL() { return URL(null); } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/ServerVariable.mustache b/boat-scaffold/src/main/templates/boat-java/ServerVariable.mustache index 1978b1eb9..f8261c444 100644 --- a/boat-scaffold/src/main/templates/boat-java/ServerVariable.mustache +++ b/boat-scaffold/src/main/templates/boat-java/ServerVariable.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.HashSet; @@ -5,6 +7,8 @@ import java.util.HashSet; /** * Representing a Server Variable for server URL template substitution. */ +{{>generatedAnnotation}} + public class ServerVariable { public String description; public String defaultValue; diff --git a/boat-scaffold/src/main/templates/boat-java/StringUtil.mustache b/boat-scaffold/src/main/templates/boat-java/StringUtil.mustache index ce52c8238..31af8371f 100644 --- a/boat-scaffold/src/main/templates/boat-java/StringUtil.mustache +++ b/boat-scaffold/src/main/templates/boat-java/StringUtil.mustache @@ -2,7 +2,11 @@ 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). @@ -47,4 +51,23 @@ public class StringUtil { } 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/boat-scaffold/src/main/templates/boat-java/additionalModelTypeAnnotations.mustache b/boat-scaffold/src/main/templates/boat-java/additionalModelTypeAnnotations.mustache index e5dc1ac12..f4871c02c 100644 --- a/boat-scaffold/src/main/templates/boat-java/additionalModelTypeAnnotations.mustache +++ b/boat-scaffold/src/main/templates/boat-java/additionalModelTypeAnnotations.mustache @@ -1,2 +1,2 @@ -{{#additionalModelTypeAnnotations}}{{.}} +{{#additionalModelTypeAnnotations}}{{{.}}} {{/additionalModelTypeAnnotations}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/additional_properties.mustache new file mode 100644 index 000000000..04c226bce --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/additional_properties.mustache @@ -0,0 +1,34 @@ +{{#jackson}} +{{#additionalPropertiesType}} + /** + * Set the additional (undeclared) property with the specified name and value. + * Creates the property if it does not already exist, otherwise replaces 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) { + this.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) properties. + * @return the additional (undeclared) properties + */ + @JsonAnyGetter + public Map getAdditionalProperties() { + return this; + } + + /** + * 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) { + return this.get(key); + } +{{/additionalPropertiesType}} +{{/jackson}} diff --git a/boat-scaffold/src/main/templates/boat-java/api.mustache b/boat-scaffold/src/main/templates/boat-java/api.mustache index 099d41cd2..3cfd92db0 100644 --- a/boat-scaffold/src/main/templates/boat-java/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/api.mustache @@ -13,20 +13,14 @@ import {{invokerPackage}}.Pair; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} {{>generatedAnnotation}} + {{#operations}} -{{#useBeanValidation}} -{{#useClassLevelBeanValidation}} -@Validated -{{/useClassLevelBeanValidation}} -{{/useBeanValidation}} public class {{classname}} { private ApiClient apiClient; @@ -51,7 +45,7 @@ public class {{classname}} { * {{summary}} * {{notes}} {{#allParams}} - * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} {{/allParams}} {{#returnType}} * @return {{.}} @@ -68,7 +62,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { Object localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; {{#allParams}}{{#required}} // verify the required parameter '{{paramName}}' is set @@ -81,11 +75,11 @@ public class {{classname}} { .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; // query params - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}List localVarCollectionQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + 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}})); @@ -124,4 +118,4 @@ public class {{classname}} { } {{/operation}} } -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/apiException.mustache index 30e171fba..93e28ec89 100644 --- a/boat-scaffold/src/main/templates/boat-java/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/apiException.mustache @@ -6,7 +6,10 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private Map> responseHeaders = null; private String responseBody = null; @@ -86,4 +89,4 @@ public class ApiException extends{{#useRuntimeException}} RuntimeException {{/us ", responseBody='" + responseBody + '\'' + '}'; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/api_doc.mustache b/boat-scaffold/src/main/templates/boat-java/api_doc.mustache index 680095472..3d3203957 100644 --- a/boat-scaffold/src/main/templates/boat-java/api_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/api_doc.mustache @@ -105,4 +105,4 @@ public class Example { {{/responses.0}} {{/operation}} -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/api_test.mustache index 176be8069..155f46e40 100644 --- a/boat-scaffold/src/main/templates/boat-java/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/api_test.mustache @@ -5,23 +5,21 @@ package {{package}}; import {{invokerPackage}}.ApiException; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} /** * API tests for {{classname}} */ -public class {{classname}}Test { +class {{classname}}Test { private final {{classname}} api = new {{classname}}(); @@ -40,7 +38,7 @@ public class {{classname}}Test { * if the Api call fails */ @Test - public void {{operationId}}Test() throws ApiException { + void {{operationId}}Test() throws ApiException { //{{#allParams}} //{{{dataType}}} {{paramName}} = null; //{{/allParams}} @@ -50,4 +48,4 @@ public class {{classname}}Test { } {{/operation}} {{/operations}} -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/auth/ApiKeyAuth.mustache index 991ae2350..8703bedd3 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/ApiKeyAuth.mustache @@ -8,6 +8,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/auth/Authentication.mustache index 033de98d5..78bddebaf 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/Authentication.mustache @@ -7,13 +7,15 @@ import {{invokerPackage}}.Pair; import java.util.Map; import java.util.List; +{{>generatedAnnotation}} + 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); + /** + * 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/boat-scaffold/src/main/templates/boat-java/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/auth/HttpBasicAuth.mustache index 9dfe714b5..be2863d62 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/HttpBasicAuth.mustache @@ -11,6 +11,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; @@ -39,4 +40,4 @@ public class HttpBasicAuth implements Authentication { String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); headerParams.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/auth/HttpBearerAuth.mustache index 322281f87..5290e3fb7 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/HttpBearerAuth.mustache @@ -4,16 +4,18 @@ package {{invokerPackage}}.auth; import {{invokerPackage}}.Pair; -import java.util.Map; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; - private String bearerToken; + private Supplier tokenSupplier; public HttpBearerAuth(String scheme) { - this.scheme = scheme; + this.scheme = upperCaseBearer(scheme); } /** @@ -22,7 +24,7 @@ public class HttpBearerAuth implements Authentication { * @return The bearer token */ public String getBearerToken() { - return bearerToken; + return tokenSupplier.get(); } /** @@ -31,19 +33,28 @@ public class HttpBearerAuth implements Authentication { * @param bearerToken The bearer token to send in the Authorization header */ public void setBearerToken(String bearerToken) { - this.bearerToken = bearerToken; + this.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; } @Override public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { - if(bearerToken == null) { + String bearerToken = tokenSupplier != null ? tokenSupplier.get() : null; + if (bearerToken == null) { return; } - - headerParams.put("Authorization", (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); + headerParams.put("Authorization", (scheme != null ? scheme + " " : "") + bearerToken); } private static String upperCaseBearer(String scheme) { - return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; + return "bearer".equalsIgnoreCase(scheme) ? "Bearer" : scheme; } } diff --git a/boat-scaffold/src/main/templates/boat-java/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/auth/OAuth.mustache index 5cb98e7cb..a500f67cf 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/OAuth.mustache @@ -8,6 +8,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class OAuth implements Authentication { private String accessToken; diff --git a/boat-scaffold/src/main/templates/boat-java/auth/OAuthFlow.mustache b/boat-scaffold/src/main/templates/boat-java/auth/OAuthFlow.mustache index 3f14c64b1..8dd1cad4e 100644 --- a/boat-scaffold/src/main/templates/boat-java/auth/OAuthFlow.mustache +++ b/boat-scaffold/src/main/templates/boat-java/auth/OAuthFlow.mustache @@ -6,9 +6,10 @@ 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 -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/build.gradle.mustache index 4d3e795a0..d5094224f 100644 --- a/boat-scaffold/src/main/templates/boat-java/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/build.gradle.mustache @@ -57,16 +57,16 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs - classifier = 'sources' + archiveClassifier = 'sources' } artifacts { @@ -97,12 +97,12 @@ if(hasProperty('target') && target == 'android') { } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -114,15 +114,21 @@ if(hasProperty('target') && target == 'android') { ext { swagger_annotations_version = "1.6.3" - jackson_version = "2.12.6" - jackson_databind_version = "2.12.6.1" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" + {{#useBeanValidation}} + bean_validation_version = "3.0.2" + {{/useBeanValidation}} jersey_version = "1.19.4" jodatime_version = "2.9.9" - junit_version = "4.13.2" + junit_version = "5.10.2" + {{#useReflectionEqualsHashCode}} + commons_lang3_version = "3.17.0" + {{/useReflectionEqualsHashCode}} } dependencies { @@ -142,5 +148,27 @@ dependencies { {{/joda}} implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$bean_validation_version" + {{/useBeanValidation}} + {{#useReflectionEqualsHashCode}} + implementation "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/useReflectionEqualsHashCode}} testImplementation "junit:junit:$junit_version" -} \ No newline at end of file + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" +} + +test { + // Enable JUnit 5 (Gradle 4.6+). + useJUnitPlatform() + + // Always run tests, even when nothing changed. + dependsOn 'cleanTest' + + // Show test results. + testLogging { + events "passed", "skipped", "failed" + } + +} diff --git a/boat-scaffold/src/main/templates/boat-java/generatedAnnotation.mustache b/boat-scaffold/src/main/templates/boat-java/generatedAnnotation.mustache index b3147c04f..e05689d5f 100644 --- a/boat-scaffold/src/main/templates/boat-java/generatedAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-java/generatedAnnotation.mustache @@ -1 +1 @@ -{{^hideGenerationTimestamp}}@{{^useJakartaEe}}javax{{/useJakartaEe}}{{#useJakartaEe}}jakarta{{/useJakartaEe}}.annotation.Generated(value = "{{generatorClass}}", date = "{{generatedDate}}"){{/hideGenerationTimestamp}} \ No newline at end of file +@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}, comments = "Generator version: {{generatorVersion}}") \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/git_push.sh.mustache b/boat-scaffold/src/main/templates/boat-java/git_push.sh.mustache old mode 100755 new mode 100644 index 9ff0e8c65..0e3776ae6 --- a/boat-scaffold/src/main/templates/boat-java/git_push.sh.mustache +++ b/boat-scaffold/src/main/templates/boat-java/git_push.sh.mustache @@ -54,4 +54,4 @@ 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' \ No newline at end of file +git push origin master 2>&1 | grep -v 'To https' diff --git a/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.jar b/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c z&{(nX_ht=QbJ%DzAya}!&uNu!4V0xI)QE$SY__m)SAKcN0P(&JcoK*Lxr@P zY&P=}&B3*UWNlc|&$Oh{BEqwK2+N2U$4WB7Fd|aIal`FGANUa9E-O)!gV`((ZGCc$ zBJA|FFrlg~9OBp#f7aHodCe{6= zay$6vN~zj1ddMZ9gQ4p32(7wD?(dE>KA2;SOzXRmPBiBc6g`eOsy+pVcHu=;Yd8@{ zSGgXf@%sKKQz~;!J;|2fC@emm#^_rnO0esEn^QxXgJYd`#FPWOUU5b;9eMAF zZhfiZb|gk8aJIw*YLp4!*(=3l8Cp{(%p?ho22*vN9+5NLV0TTazNY$B5L6UKUrd$n zjbX%#m7&F#U?QNOBXkiiWB*_tk+H?N3`vg;1F-I+83{M2!8<^nydGr5XX}tC!10&e z7D36bLaB56WrjL&HiiMVtpff|K%|*{t*ltt^5ood{FOG0<>k&1h95qPio)2`eL${YAGIx(b4VN*~nKn6E~SIQUuRH zQ+5zP6jfnP$S0iJ@~t!Ai3o`X7biohli;E zT#yXyl{bojG@-TGZzpdVDXhbmF%F9+-^YSIv|MT1l3j zrxOFq>gd2%U}?6}8mIj?M zc077Zc9fq(-)4+gXv?Az26IO6eV`RAJz8e3)SC7~>%rlzDwySVx*q$ygTR5kW2ds- z!HBgcq0KON9*8Ff$X0wOq$`T7ml(@TF)VeoF}x1OttjuVHn3~sHrMB++}f7f9H%@f z=|kP_?#+fve@{0MlbkC9tyvQ_R?lRdRJ@$qcB(8*jyMyeME5ns6ypVI1Xm*Zr{DuS zZ!1)rQfa89c~;l~VkCiHI|PCBd`S*2RLNQM8!g9L6?n`^evQNEwfO@&JJRme+uopQX0%Jo zgd5G&#&{nX{o?TQwQvF1<^Cg3?2co;_06=~Hcb6~4XWpNFL!WU{+CK;>gH%|BLOh7@!hsa(>pNDAmpcuVO-?;Bic17R}^|6@8DahH)G z!EmhsfunLL|3b=M0MeK2vqZ|OqUqS8npxwge$w-4pFVXFq$_EKrZY?BuP@Az@(k`L z`ViQBSk`y+YwRT;&W| z2e3UfkCo^uTA4}Qmmtqs+nk#gNr2W4 zTH%hhErhB)pkXR{B!q5P3-OM+M;qu~f>}IjtF%>w{~K-0*jPVLl?Chz&zIdxp}bjx zStp&Iufr58FTQ36AHU)0+CmvaOpKF;W@sMTFpJ`j;3d)J_$tNQI^c<^1o<49Z(~K> z;EZTBaVT%14(bFw2ob@?JLQ2@(1pCdg3S%E4*dJ}dA*v}_a4_P(a`cHnBFJxNobAv zf&Zl-Yt*lhn-wjZsq<9v-IsXxAxMZ58C@e0!rzhJ+D@9^3~?~yllY^s$?&oNwyH!#~6x4gUrfxplCvK#!f z$viuszW>MFEcFL?>ux*((!L$;R?xc*myjRIjgnQX79@UPD$6Dz0jutM@7h_pq z0Zr)#O<^y_K6jfY^X%A-ip>P%3saX{!v;fxT-*0C_j4=UMH+Xth(XVkVGiiKE#f)q z%Jp=JT)uy{&}Iq2E*xr4YsJ5>w^=#-mRZ4vPXpI6q~1aFwi+lQcimO45V-JXP;>(Q zo={U`{=_JF`EQj87Wf}{Qy35s8r1*9Mxg({CvOt}?Vh9d&(}iI-quvs-rm~P;eRA@ zG5?1HO}puruc@S{YNAF3vmUc2B4!k*yi))<5BQmvd3tr}cIs#9)*AX>t`=~{f#Uz0 z0&Nk!7sSZwJe}=)-R^$0{yeS!V`Dh7w{w5rZ9ir!Z7Cd7dwZcK;BT#V0bzTt>;@Cl z#|#A!-IL6CZ@eHH!CG>OO8!%G8&8t4)Ro@}USB*k>oEUo0LsljsJ-%5Mo^MJF2I8- z#v7a5VdJ-Cd%(a+y6QwTmi+?f8Nxtm{g-+WGL>t;s#epv7ug>inqimZCVm!uT5Pf6 ziEgQt7^%xJf#!aPWbuC_3Nxfb&CFbQy!(8ANpkWLI4oSnH?Q3f?0k1t$3d+lkQs{~(>06l&v|MpcFsyAv zin6N!-;pggosR*vV=DO(#+}4ps|5$`udE%Kdmp?G7B#y%H`R|i8skKOd9Xzx8xgR$>Zo2R2Ytktq^w#ul4uicxW#{ zFjG_RNlBroV_n;a7U(KIpcp*{M~e~@>Q#Av90Jc5v%0c>egEdY4v3%|K1XvB{O_8G zkTWLC>OZKf;XguMH2-Pw{BKbFzaY;4v2seZV0>^7Q~d4O=AwaPhP3h|!hw5aqOtT@ z!SNz}$of**Bl3TK209@F=Tn1+mgZa8yh(Png%Zd6Mt}^NSjy)etQrF zme*llAW=N_8R*O~d2!apJnF%(JcN??=`$qs3Y+~xs>L9x`0^NIn!8mMRFA_tg`etw z3k{9JAjnl@ygIiJcNHTy02GMAvBVqEss&t2<2mnw!; zU`J)0>lWiqVqo|ex7!+@0i>B~BSU1A_0w#Ee+2pJx0BFiZ7RDHEvE*ptc9md(B{&+ zKE>TM)+Pd>HEmdJao7U@S>nL(qq*A)#eLOuIfAS@j`_sK0UEY6OAJJ-kOrHG zjHx`g!9j*_jRcJ%>CE9K2MVf?BUZKFHY?EpV6ai7sET-tqk=nDFh-(65rhjtlKEY% z@G&cQ<5BKatfdA1FKuB=i>CCC5(|9TMW%K~GbA4}80I5%B}(gck#Wlq@$nO3%@QP_ z8nvPkJFa|znk>V92cA!K1rKtr)skHEJD;k8P|R8RkCq1Rh^&}Evwa4BUJz2f!2=MH zo4j8Y$YL2313}H~F7@J7mh>u%556Hw0VUOz-Un@ZASCL)y8}4XXS`t1AC*^>PLwIc zUQok5PFS=*#)Z!3JZN&eZ6ZDP^-c@StY*t20JhCnbMxXf=LK#;`4KHEqMZ-Ly9KsS zI2VUJGY&PmdbM+iT)zek)#Qc#_i4uH43 z@T5SZBrhNCiK~~esjsO9!qBpaWK<`>!-`b71Y5ReXQ4AJU~T2Njri1CEp5oKw;Lnm)-Y@Z3sEY}XIgSy%xo=uek(kAAH5MsV$V3uTUsoTzxp_rF=tx zV07vlJNKtJhCu`b}*#m&5LV4TAE&%KtHViDAdv#c^x`J7bg z&N;#I2GkF@SIGht6p-V}`!F_~lCXjl1BdTLIjD2hH$J^YFN`7f{Q?OHPFEM$65^!u zNwkelo*5+$ZT|oQ%o%;rBX$+?xhvjb)SHgNHE_yP%wYkkvXHS{Bf$OiKJ5d1gI0j< zF6N}Aq=(WDo(J{e-uOecxPD>XZ@|u-tgTR<972`q8;&ZD!cep^@B5CaqFz|oU!iFj zU0;6fQX&~15E53EW&w1s9gQQ~Zk16X%6 zjG`j0yq}4deX2?Tr(03kg>C(!7a|b9qFI?jcE^Y>-VhudI@&LI6Qa}WQ>4H_!UVyF z((cm&!3gmq@;BD#5P~0;_2qgZhtJS|>WdtjY=q zLnHH~Fm!cxw|Z?Vw8*~?I$g#9j&uvgm7vPr#&iZgPP~v~BI4jOv;*OQ?jYJtzO<^y z7-#C={r7CO810!^s(MT!@@Vz_SVU)7VBi(e1%1rvS!?PTa}Uv`J!EP3s6Y!xUgM^8 z4f!fq<3Wer_#;u!5ECZ|^c1{|q_lh3m^9|nsMR1#Qm|?4Yp5~|er2?W^7~cl;_r4WSme_o68J9p03~Hc%X#VcX!xAu%1`R!dfGJCp zV*&m47>s^%Ib0~-2f$6oSgn3jg8m%UA;ArcdcRyM5;}|r;)?a^D*lel5C`V5G=c~k zy*w_&BfySOxE!(~PI$*dwG><+-%KT5p?whOUMA*k<9*gi#T{h3DAxzAPxN&Xws8o9Cp*`PA5>d9*Z-ynV# z9yY*1WR^D8|C%I@vo+d8r^pjJ$>eo|j>XiLWvTWLl(^;JHCsoPgem6PvegHb-OTf| zvTgsHSa;BkbG=(NgPO|CZu9gUCGr$8*EoH2_Z#^BnxF0yM~t`|9ws_xZ8X8iZYqh! zAh;HXJ)3P&)Q0(&F>!LN0g#bdbis-cQxyGn9Qgh`q+~49Fqd2epikEUw9caM%V6WgP)532RMRW}8gNS%V%Hx7apSz}tn@bQy!<=lbhmAH=FsMD?leawbnP5BWM0 z5{)@EEIYMu5;u)!+HQWhQ;D3_Cm_NADNeb-f56}<{41aYq8p4=93d=-=q0Yx#knGYfXVt z+kMxlus}t2T5FEyCN~!}90O_X@@PQpuy;kuGz@bWft%diBTx?d)_xWd_-(!LmVrh**oKg!1CNF&LX4{*j|) zIvjCR0I2UUuuEXh<9}oT_zT#jOrJAHNLFT~Ilh9hGJPI1<5`C-WA{tUYlyMeoy!+U zhA#=p!u1R7DNg9u4|QfED-2TuKI}>p#2P9--z;Bbf4Op*;Q9LCbO&aL2i<0O$ByoI z!9;Ght733FC>Pz>$_mw(F`zU?`m@>gE`9_p*=7o=7av`-&ifU(^)UU`Kg3Kw`h9-1 z6`e6+im=|m2v`pN(2dE%%n8YyQz;#3Q-|x`91z?gj68cMrHl}C25|6(_dIGk*8cA3 zRHB|Nwv{@sP4W+YZM)VKI>RlB`n=Oj~Rzx~M+Khz$N$45rLn6k1nvvD^&HtsMA4`s=MmuOJID@$s8Ph4E zAmSV^+s-z8cfv~Yd(40Sh4JG#F~aB>WFoX7ykaOr3JaJ&Lb49=B8Vk-SQT9%7TYhv z?-Pprt{|=Y5ZQ1?od|A<_IJU93|l4oAfBm?3-wk{O<8ea+`}u%(kub(LFo2zFtd?4 zwpN|2mBNywv+d^y_8#<$r>*5+$wRTCygFLcrwT(qc^n&@9r+}Kd_u@Ithz(6Qb4}A zWo_HdBj#V$VE#l6pD0a=NfB0l^6W^g`vm^sta>Tly?$E&{F?TTX~DsKF~poFfmN%2 z4x`Dc{u{Lkqz&y!33;X}weD}&;7p>xiI&ZUb1H9iD25a(gI|`|;G^NwJPv=1S5e)j z;U;`?n}jnY6rA{V^ zxTd{bK)Gi^odL3l989DQlN+Zs39Xe&otGeY(b5>rlIqfc7Ap4}EC?j<{M=hlH{1+d zw|c}}yx88_xQr`{98Z!d^FNH77=u(p-L{W6RvIn40f-BldeF-YD>p6#)(Qzf)lfZj z?3wAMtPPp>vMehkT`3gToPd%|D8~4`5WK{`#+}{L{jRUMt zrFz+O$C7y8$M&E4@+p+oV5c%uYzbqd2Y%SSgYy#xh4G3hQv>V*BnuKQhBa#=oZB~w{azUB+q%bRe_R^ z>fHBilnRTUfaJ201czL8^~Ix#+qOHSO)A|xWLqOxB$dT2W~)e-r9;bm=;p;RjYahB z*1hegN(VKK+ztr~h1}YP@6cfj{e#|sS`;3tJhIJK=tVJ-*h-5y9n*&cYCSdg#EHE# zSIx=r#qOaLJoVVf6v;(okg6?*L_55atl^W(gm^yjR?$GplNP>BZsBYEf_>wM0Lc;T zhf&gpzOWNxS>m+mN92N0{;4uw`P+9^*|-1~$uXpggj4- z^SFc4`uzj2OwdEVT@}Q`(^EcQ_5(ZtXTql*yGzdS&vrS_w>~~ra|Nb5abwf}Y!uq6R5f&6g2ge~2p(%c< z@O)cz%%rr4*cRJ5f`n@lvHNk@lE1a*96Kw6lJ~B-XfJW%?&-y?;E&?1AacU@`N`!O z6}V>8^%RZ7SQnZ-z$(jsX`amu*5Fj8g!3RTRwK^`2_QHe;_2y_n|6gSaGyPmI#kA0sYV<_qOZc#-2BO%hX)f$s-Z3xlI!ub z^;3ru11DA`4heAu%}HIXo&ctujzE2!6DIGE{?Zs>2}J+p&C$rc7gJC35gxhflorvsb%sGOxpuWhF)dL_&7&Z99=5M0b~Qa;Mo!j&Ti_kXW!86N%n= zSC@6Lw>UQ__F&+&Rzv?gscwAz8IP!n63>SP)^62(HK98nGjLY2*e^OwOq`3O|C92? z;TVhZ2SK%9AGW4ZavTB9?)mUbOoF`V7S=XM;#3EUpR+^oHtdV!GK^nXzCu>tpR|89 zdD{fnvCaN^^LL%amZ^}-E+214g&^56rpdc@yv0b<3}Ys?)f|fXN4oHf$six)-@<;W&&_kj z-B}M5U*1sb4)77aR=@%I?|Wkn-QJVuA96an25;~!gq(g1@O-5VGo7y&E_srxL6ZfS z*R%$gR}dyONgju*D&?geiSj7SZ@ftyA|}(*Y4KbvU!YLsi1EDQQCnb+-cM=K1io78o!v*);o<XwjaQH%)uIP&Zm?)Nfbfn;jIr z)d#!$gOe3QHp}2NBak@yYv3m(CPKkwI|{;d=gi552u?xj9ObCU^DJFQp4t4e1tPzM zvsRIGZ6VF+{6PvqsplMZWhz10YwS={?`~O0Ec$`-!klNUYtzWA^f9m7tkEzCy<_nS z=&<(awFeZvt51>@o_~>PLs05CY)$;}Oo$VDO)?l-{CS1Co=nxjqben*O1BR>#9`0^ zkwk^k-wcLCLGh|XLjdWv0_Hg54B&OzCE^3NCP}~OajK-LuRW53CkV~Su0U>zN%yQP zH8UH#W5P3-!ToO-2k&)}nFe`t+mdqCxxAHgcifup^gKpMObbox9LFK;LP3}0dP-UW z?Zo*^nrQ6*$FtZ(>kLCc2LY*|{!dUn$^RW~m9leoF|@Jy|M5p-G~j%+P0_#orRKf8 zvuu5<*XO!B?1E}-*SY~MOa$6c%2cM+xa8}_8x*aVn~57v&W(0mqN1W`5a7*VN{SUH zXz98DDyCnX2EPl-`Lesf`=AQT%YSDb`$%;(jUTrNen$NPJrlpPDP}prI>Ml!r6bCT;mjsg@X^#&<}CGf0JtR{Ecwd&)2zuhr#nqdgHj+g2n}GK9CHuwO zk>oZxy{vcOL)$8-}L^iVfJHAGfwN$prHjYV0ju}8%jWquw>}_W6j~m<}Jf!G?~r5&Rx)!9JNX!ts#SGe2HzobV5); zpj@&`cNcO&q+%*<%D7za|?m5qlmFK$=MJ_iv{aRs+BGVrs)98BlN^nMr{V_fcl_;jkzRju+c-y?gqBC_@J0dFLq-D9@VN&-`R9U;nv$Hg?>$oe4N&Ht$V_(JR3TG^! zzJsbQbi zFE6-{#9{G{+Z}ww!ycl*7rRdmU#_&|DqPfX3CR1I{Kk;bHwF6jh0opI`UV2W{*|nn zf_Y@%wW6APb&9RrbEN=PQRBEpM(N1w`81s=(xQj6 z-eO0k9=Al|>Ej|Mw&G`%q8e$2xVz1v4DXAi8G};R$y)ww638Y=9y$ZYFDM$}vzusg zUf+~BPX>(SjA|tgaFZr_e0{)+z9i6G#lgt=F_n$d=beAt0Sa0a7>z-?vcjl3e+W}+ z1&9=|vC=$co}-Zh*%3588G?v&U7%N1Qf-wNWJ)(v`iO5KHSkC5&g7CrKu8V}uQGcfcz zmBz#Lbqwqy#Z~UzHgOQ;Q-rPxrRNvl(&u6ts4~0=KkeS;zqURz%!-ERppmd%0v>iRlEf+H$yl{_8TMJzo0 z>n)`On|7=WQdsqhXI?#V{>+~}qt-cQbokEbgwV3QvSP7&hK4R{Z{aGHVS3;+h{|Hz z6$Js}_AJr383c_+6sNR|$qu6dqHXQTc6?(XWPCVZv=)D#6_;D_8P-=zOGEN5&?~8S zl5jQ?NL$c%O)*bOohdNwGIKM#jSAC?BVY={@A#c9GmX0=T(0G}xs`-%f3r=m6-cpK z!%waekyAvm9C3%>sixdZj+I(wQlbB4wv9xKI*T13DYG^T%}zZYJ|0$Oj^YtY+d$V$ zAVudSc-)FMl|54n=N{BnZTM|!>=bhaja?o7s+v1*U$!v!qQ%`T-6fBvmdPbVmro&d zk07TOp*KuxRUSTLRrBj{mjsnF8`d}rMViY8j`jo~Hp$fkv9F_g(jUo#Arp;Xw0M$~ zRIN!B22~$kx;QYmOkos@%|5k)!QypDMVe}1M9tZfkpXKGOxvKXB!=lo`p?|R1l=tA zp(1}c6T3Fwj_CPJwVsYtgeRKg?9?}%oRq0F+r+kdB=bFUdVDRPa;E~~>2$w}>O>v=?|e>#(-Lyx?nbg=ckJ#5U6;RT zNvHhXk$P}m9wSvFyU3}=7!y?Y z=fg$PbV8d7g25&-jOcs{%}wTDKm>!Vk);&rr;O1nvO0VrU&Q?TtYVU=ir`te8SLlS zKSNmV=+vF|ATGg`4$N1uS|n??f}C_4Sz!f|4Ly8#yTW-FBfvS48Tef|-46C(wEO_%pPhUC5$-~Y?!0vFZ^Gu`x=m7X99_?C-`|h zfmMM&Y@zdfitA@KPw4Mc(YHcY1)3*1xvW9V-r4n-9ZuBpFcf{yz+SR{ zo$ZSU_|fgwF~aakGr(9Be`~A|3)B=9`$M-TWKipq-NqRDRQc}ABo*s_5kV%doIX7LRLRau_gd@Rd_aLFXGSU+U?uAqh z8qusWWcvgQ&wu{|sRXmv?sl=xc<$6AR$+cl& zFNh5q1~kffG{3lDUdvEZu5c(aAG~+64FxdlfwY^*;JSS|m~CJusvi-!$XR`6@XtY2 znDHSz7}_Bx7zGq-^5{stTRy|I@N=>*y$zz>m^}^{d&~h;0kYiq8<^Wq7Dz0w31ShO^~LUfW6rfitR0(=3;Uue`Y%y@ex#eKPOW zO~V?)M#AeHB2kovn1v=n^D?2{2jhIQd9t|_Q+c|ZFaWt+r&#yrOu-!4pXAJuxM+Cx z*H&>eZ0v8Y`t}8{TV6smOj=__gFC=eah)mZt9gwz>>W$!>b3O;Rm^Ig*POZP8Rl0f zT~o=Nu1J|lO>}xX&#P58%Yl z83`HRs5#32Qm9mdCrMlV|NKNC+Z~ z9OB8xk5HJ>gBLi+m@(pvpw)1(OaVJKs*$Ou#@Knd#bk+V@y;YXT?)4eP9E5{J%KGtYinNYJUH9PU3A}66c>Xn zZ{Bn0<;8$WCOAL$^NqTjwM?5d=RHgw3!72WRo0c;+houoUA@HWLZM;^U$&sycWrFd zE7ekt9;kb0`lps{>R(}YnXlyGY}5pPd9zBpgXeJTY_jwaJGSJQC#-KJqmh-;ad&F- z-Y)E>!&`Rz!HtCz>%yOJ|v(u7P*I$jqEY3}(Z-orn4 zlI?CYKNl`6I){#2P1h)y(6?i;^z`N3bxTV%wNvQW+eu|x=kbj~s8rhCR*0H=iGkSj zk23lr9kr|p7#qKL=UjgO`@UnvzU)`&fI>1Qs7ubq{@+lK{hH* zvl6eSb9%yngRn^T<;jG1SVa)eA>T^XX=yUS@NCKpk?ovCW1D@!=@kn;l_BrG;hOTC z6K&H{<8K#dI(A+zw-MWxS+~{g$tI7|SfP$EYKxA}LlVO^sT#Oby^grkdZ^^lA}uEF zBSj$weBJG{+Bh@Yffzsw=HyChS(dtLE3i*}Zj@~!_T-Ay7z=B)+*~3|?w`Zd)Co2t zC&4DyB!o&YgSw+fJn6`sn$e)29`kUwAc+1MND7YjV%lO;H2}fNy>hD#=gT ze+-aFNpyKIoXY~Vq-}OWPBe?Rfu^{ps8>Xy%42r@RV#*QV~P83jdlFNgkPN=T|Kt7 zV*M`Rh*30&AWlb$;ae130e@}Tqi3zx2^JQHpM>j$6x`#{mu%tZlwx9Gj@Hc92IuY* zarmT|*d0E~vt6<+r?W^UW0&#U&)8B6+1+;k^2|FWBRP9?C4Rk)HAh&=AS8FS|NQaZ z2j!iZ)nbEyg4ZTp-zHwVlfLC~tXIrv(xrP8PAtR{*c;T24ycA-;auWsya-!kF~CWZ zw_uZ|%urXgUbc@x=L=_g@QJ@m#5beS@6W195Hn7>_}z@Xt{DIEA`A&V82bc^#!q8$ zFh?z_Vn|ozJ;NPd^5uu(9tspo8t%&-U9Ckay-s@DnM*R5rtu|4)~e)`z0P-sy?)kc zs_k&J@0&0!q4~%cKL)2l;N*T&0;mqX5T{Qy60%JtKTQZ-xb%KOcgqwJmb%MOOKk7N zgq})R_6**{8A|6H?fO+2`#QU)p$Ei2&nbj6TpLSIT^D$|`TcSeh+)}VMb}LmvZ{O| ze*1IdCt3+yhdYVxcM)Q_V0bIXLgr6~%JS<<&dxIgfL=Vnx4YHuU@I34JXA|+$_S3~ zy~X#gO_X!cSs^XM{yzDGNM>?v(+sF#<0;AH^YrE8smx<36bUsHbN#y57K8WEu(`qHvQ6cAZPo=J5C(lSmUCZ57Rj6cx!e^rfaI5%w}unz}4 zoX=nt)FVNV%QDJH`o!u9olLD4O5fl)xp+#RloZlaA92o3x4->?rB4`gS$;WO{R;Z3>cG3IgFX2EA?PK^M}@%1%A;?f6}s&CV$cIyEr#q5;yHdNZ9h{| z-=dX+a5elJoDo?Eq&Og!nN6A)5yYpnGEp}?=!C-V)(*~z-+?kY1Q7qs#Rsy%hu_60rdbB+QQNr?S1 z?;xtjUv|*E3}HmuNyB9aFL5H~3Ho0UsmuMZELp1a#CA1g`P{-mT?BchuLEtK}!QZ=3AWakRu~?f9V~3F;TV`5%9Pcs_$gq&CcU}r8gOO zC2&SWPsSG{&o-LIGTBqp6SLQZPvYKp$$7L4WRRZ0BR$Kf0I0SCFkqveCp@f)o8W)! z$%7D1R`&j7W9Q9CGus_)b%+B#J2G;l*FLz#s$hw{BHS~WNLODV#(!u_2Pe&tMsq={ zdm7>_WecWF#D=?eMjLj=-_z`aHMZ=3_-&E8;ibPmM}61i6J3is*=dKf%HC>=xbj4$ zS|Q-hWQ8T5mWde6h@;mS+?k=89?1FU<%qH9B(l&O>k|u_aD|DY*@~(`_pb|B#rJ&g zR0(~(68fpUPz6TdS@4JT5MOPrqDh5_H(eX1$P2SQrkvN8sTxwV>l0)Qq z0pzTuvtEAKRDkKGhhv^jk%|HQ1DdF%5oKq5BS>szk-CIke{%js?~%@$uaN3^Uz6Wf z_iyx{bZ(;9y4X&>LPV=L=d+A}7I4GkK0c1Xts{rrW1Q7apHf-))`BgC^0^F(>At1* za@e7{lq%yAkn*NH8Q1{@{lKhRg*^TfGvv!Sn*ed*x@6>M%aaqySxR|oNadYt1mpUZ z6H(rupHYf&Z z29$5g#|0MX#aR6TZ$@eGxxABRKakDYtD%5BmKp;HbG_ZbT+=81E&=XRk6m_3t9PvD zr5Cqy(v?gHcYvYvXkNH@S#Po~q(_7MOuCAB8G$a9BC##gw^5mW16cML=T=ERL7wsk zzNEayTG?mtB=x*wc@ifBCJ|irFVMOvH)AFRW8WE~U()QT=HBCe@s$dA9O!@`zAAT) zaOZ7l6vyR+Nk_OOF!ZlZmjoImKh)dxFbbR~z(cMhfeX1l7S_`;h|v3gI}n9$sSQ>+3@AFAy9=B_y$)q;Wdl|C-X|VV3w8 z2S#>|5dGA8^9%Bu&fhmVRrTX>Z7{~3V&0UpJNEl0=N32euvDGCJ>#6dUSi&PxFW*s zS`}TB>?}H(T2lxBJ!V#2taV;q%zd6fOr=SGHpoSG*4PDaiG0pdb5`jelVipkEk%FV zThLc@Hc_AL1#D&T4D=w@UezYNJ%0=f3iVRuVL5H?eeZM}4W*bomebEU@e2d`M<~uW zf#Bugwf`VezG|^Qbt6R_=U0}|=k;mIIakz99*>FrsQR{0aQRP6ko?5<7bkDN8evZ& zB@_KqQG?ErKL=1*ZM9_5?Pq%lcS4uLSzN(Mr5=t6xHLS~Ym`UgM@D&VNu8e?_=nSFtF$u@hpPSmI4Vo_t&v?>$~K4y(O~Rb*(MFy_igM7 z*~yYUyR6yQgzWnWMUgDov!!g=lInM+=lOmOk4L`O?{i&qxy&D*_qorRbDwj6?)!ef z#JLd7F6Z2I$S0iYI={rZNk*<{HtIl^mx=h>Cim*04K4+Z4IJtd*-)%6XV2(MCscPiw_a+y*?BKbTS@BZ3AUao^%Zi#PhoY9Vib4N>SE%4>=Jco0v zH_Miey{E;FkdlZSq)e<{`+S3W=*ttvD#hB8w=|2aV*D=yOV}(&p%0LbEWH$&@$X3x~CiF-?ejQ*N+-M zc8zT@3iwkdRT2t(XS`d7`tJQAjRmKAhiw{WOqpuvFp`i@Q@!KMhwKgsA}%@sw8Xo5Y=F zhRJZg)O4uqNWj?V&&vth*H#je6T}}p_<>!Dr#89q@uSjWv~JuW(>FqoJ5^ho0%K?E z9?x_Q;kmcsQ@5=}z@tdljMSt9-Z3xn$k)kEjK|qXS>EfuDmu(Z8|(W?gY6-l z@R_#M8=vxKMAoi&PwnaIYw2COJM@atcgfr=zK1bvjW?9B`-+Voe$Q+H$j!1$Tjn+* z&LY<%)L@;zhnJlB^Og6I&BOR-m?{IW;tyYC%FZ!&Z>kGjHJ6cqM-F z&19n+e1=9AH1VrVeHrIzqlC`w9=*zfmrerF?JMzO&|Mmv;!4DKc(sp+jy^Dx?(8>1 zH&yS_4yL7m&GWX~mdfgH*AB4{CKo;+egw=PrvkTaoBU+P-4u?E|&!c z)DKc;>$$B6u*Zr1SjUh2)FeuWLWHl5TH(UHWkf zLs>7px!c5n;rbe^lO@qlYLzlDVp(z?6rPZel=YB)Uv&n!2{+Mb$-vQl=xKw( zve&>xYx+jW_NJh!FV||r?;hdP*jOXYcLCp>DOtJ?2S^)DkM{{Eb zS$!L$e_o0(^}n3tA1R3-$SNvgBq;DOEo}fNc|tB%%#g4RA3{|euq)p+xd3I8^4E&m zFrD%}nvG^HUAIKe9_{tXB;tl|G<%>yk6R;8L2)KUJw4yHJXUOPM>(-+jxq4R;z8H#>rnJy*)8N+$wA$^F zN+H*3t)eFEgxLw+Nw3};4WV$qj&_D`%ADV2%r zJCPCo%{=z7;`F98(us5JnT(G@sKTZ^;2FVitXyLe-S5(hV&Ium+1pIUB(CZ#h|g)u zSLJJ<@HgrDiA-}V_6B^x1>c9B6%~847JkQ!^KLZ2skm;q*edo;UA)~?SghG8;QbHh z_6M;ouo_1rq9=x$<`Y@EA{C%6-pEV}B(1#sDoe_e1s3^Y>n#1Sw;N|}8D|s|VPd+g z-_$QhCz`vLxxrVMx3ape1xu3*wjx=yKSlM~nFgkNWb4?DDr*!?U)L_VeffF<+!j|b zZ$Wn2$TDv3C3V@BHpSgv3JUif8%hk%OsGZ=OxH@8&4`bbf$`aAMchl^qN>Eyu3JH} z9-S!x8-s4fE=lad%Pkp8hAs~u?|uRnL48O|;*DEU! zuS0{cpk%1E0nc__2%;apFsTm0bKtd&A0~S3Cj^?72-*Owk3V!ZG*PswDfS~}2<8le z5+W^`Y(&R)yVF*tU_s!XMcJS`;(Tr`J0%>p=Z&InR%D3@KEzzI+-2)HK zuoNZ&o=wUC&+*?ofPb0a(E6(<2Amd6%uSu_^-<1?hsxs~0K5^f(LsGqgEF^+0_H=uNk9S0bb!|O8d?m5gQjUKevPaO+*VfSn^2892K~%crWM8+6 z25@V?Y@J<9w%@NXh-2!}SK_(X)O4AM1-WTg>sj1{lj5@=q&dxE^9xng1_z9w9DK>| z6Iybcd0e zyi;Ew!KBRIfGPGytQ6}z}MeXCfLY0?9%RiyagSp_D1?N&c{ zyo>VbJ4Gy`@Fv+5cKgUgs~na$>BV{*em7PU3%lloy_aEovR+J7TfQKh8BJXyL6|P8un-Jnq(ghd!_HEOh$zlv2$~y3krgeH;9zC}V3f`uDtW(%mT#944DQa~^8ZI+zAUu4U(j0YcDfKR$bK#gvn_{JZ>|gZ5+)u?T$w7Q%F^;!Wk?G z(le7r!ufT*cxS}PR6hIVtXa)i`d$-_1KkyBU>qmgz-=T};uxx&sKgv48akIWQ89F{ z0XiY?WM^~;|T8zBOr zs#zuOONzH?svv*jokd5SK8wG>+yMC)LYL|vLqm^PMHcT=`}V$=nIRHe2?h)8WQa6O zPAU}d`1y(>kZiP~Gr=mtJLMu`i<2CspL|q2DqAgAD^7*$xzM`PU4^ga`ilE134XBQ z99P(LhHU@7qvl9Yzg$M`+dlS=x^(m-_3t|h>S}E0bcFMn=C|KamQ)=w2^e)35p`zY zRV8X?d;s^>Cof2SPR&nP3E+-LCkS0J$H!eh8~k0qo$}00b=7!H_I2O+Ro@3O$nPdm ztmbOO^B+IHzQ5w>@@@J4cKw5&^_w6s!s=H%&byAbUtczPQ7}wfTqxxtQNfn*u73Qw zGuWsrky_ajPx-5`R<)6xHf>C(oqGf_Fw|-U*GfS?xLML$kv;h_pZ@Kk$y0X(S+K80 z6^|z)*`5VUkawg}=z`S;VhZhxyDfrE0$(PMurAxl~<>lfZa>JZ288ULK7D` zl9|#L^JL}Y$j*j`0-K6kH#?bRmg#5L3iB4Z)%iF@SqT+Lp|{i`m%R-|ZE94Np7Pa5 zCqC^V3}B(FR340pmF*qaa}M}+h6}mqE~7Sh!9bDv9YRT|>vBNAqv09zXHMlcuhKD| zcjjA(b*XCIwJ33?CB!+;{)vX@9xns_b-VO{i0y?}{!sdXj1GM8+$#v>W7nw;+O_9B z_{4L;C6ol?(?W0<6taGEn1^uG=?Q3i29sE`RfYCaV$3DKc_;?HsL?D_fSYg}SuO5U zOB_f4^vZ_x%o`5|C@9C5+o=mFy@au{s)sKw!UgC&L35aH(sgDxRE2De%(%OT=VUdN ziVLEmdOvJ&5*tCMKRyXctCwQu_RH%;m*$YK&m;jtbdH#Ak~13T1^f89tn`A%QEHWs~jnY~E}p_Z$XC z=?YXLCkzVSK+Id`xZYTegb@W8_baLt-Fq`Tv|=)JPbFsKRm)4UW;yT+J`<)%#ue9DPOkje)YF2fsCilK9MIIK>p*`fkoD5nGfmLwt)!KOT+> zOFq*VZktDDyM3P5UOg`~XL#cbzC}eL%qMB=Q5$d89MKuN#$6|4gx_Jt0Gfn8w&q}%lq4QU%6#jT*MRT% zrLz~C8FYKHawn-EQWN1B75O&quS+Z81(zN)G>~vN8VwC+e+y(`>HcxC{MrJ;H1Z4k zZWuv$w_F0-Ub%MVcpIc){4PGL^I7M{>;hS?;eH!;gmcOE66z3;Z1Phqo(t zVP(Hg6q#0gIKgsg7L7WE!{Y#1nI(45tx2{$34dDd#!Z0NIyrm)HOn5W#7;f4pQci# zDW!FI(g4e668kI9{2+mLwB+=#9bfqgX%!B34V-$wwSN(_cm*^{y0jQtv*4}eO^sOV z*9xoNvX)c9isB}Tgx&ZRjp3kwhTVK?r9;n!x>^XYT z@Q^7zp{rkIs{2mUSE^2!Gf6$6;j~&4=-0cSJJDizZp6LTe8b45;{AKM%v99}{{FfC zz709%u0mC=1KXTo(=TqmZQ;c?$M3z(!xah>aywrj40sc2y3rKFw4jCq+Y+u=CH@_V zxz|qeTwa>+<|H%8Dz5u>ZI5MmjTFwXS-Fv!TDd*`>3{krWoNVx$<133`(ftS?ZPyY z&4@ah^3^i`vL$BZa>O|Nt?ucewzsF)0zX3qmM^|waXr=T0pfIb0*$AwU=?Ipl|1Y; z*Pk6{C-p4MY;j@IJ|DW>QHZQJcp;Z~?8(Q+Kk3^0qJ}SCk^*n4W zu9ZFwLHUx-$6xvaQ)SUQcYd6fF8&x)V`1bIuX@>{mE$b|Yd(qomn3;bPwnDUc0F=; zh*6_((%bqAYQWQ~odER?h>1mkL4kpb3s7`0m@rDKGU*oyF)$j~Ffd4fXV$?`f~rHf zB%Y)@5SXZvfwm10RY5X?TEo)PK_`L6qgBp=#>fO49$D zDq8Ozj0q6213tV5Qq=;fZ0$|KroY{Dz=l@lU^J)?Ko@ti20TRplXzphBi>XGx4bou zEWrkNjz0t5j!_ke{g5I#PUlEU$Km8g8TE|XK=MkU@PT4T><2OVamoK;wJ}3X0L$vX zgd7gNa359*nc)R-0!`2X@FOTB`+oETOPc=ubp5R)VQgY+5BTZZJ2?9QwnO=dnulIUF3gFn;BODC2)65)HeVd%t86sL7Rv^Y+nbn+&l z6BAJY(ETvwI)Ts$aiE8rht4KD*qNyE{8{x6R|%akbTBzw;2+6Echkt+W+`u^XX z_z&x%n*=4<|!MJu@}isLc4AW#{m2if&A5T5g&~ ziuMQeS*U5sL6J698wOd)K@oK@1{peP5&Esut<#VH^u)gp`9H4)`uE!2$>RTctN+^u z=ASkePDZA-X8)rp%D;p*~P?*a_=*Kwc<^>QSH|^<0>o37lt^+Mj1;4YvJ(JR-Y+?%Nu}JAYj5 z_Qc5%Ao#F?q32i?ZaN2OSNhWL;2oDEw_({7ZbgUjna!Fqn3NzLM@-EWFPZVmc>(fZ z0&bF-Ch#p9C{YJT9Rcr3+Y_uR^At1^BxZ#eo>$PLJF3=;t_$2|t+_6gg5(j{TmjYU zK12c&lE?Eh+2u2&6Gf*IdKS&6?rYbSEKBN!rv{YCm|Rt=UlPcW9j`0o6{66#y5t9C zruFA2iKd=H%jHf%ypOkxLnO8#H}#Zt{8p!oi6)7#NqoF({t6|J^?1e*oxqng9Q2Cc zg%5Vu!em)}Yuj?kaP!D?b?(C*w!1;>R=j90+RTkyEXz+9CufZ$C^umX^+4|JYaO<5 zmIM3#dv`DGM;@F6;(t!WngZSYzHx?9&$xEF70D1BvfVj<%+b#)vz)2iLCrTeYzUcL z(OBnNoG6Le%M+@2oo)&jdOg=iCszzv59e zDRCeaX8l1hC=8LbBt|k5?CXgep=3r9BXx1uR8!p%Z|0+4Xro=xi0G!e{c4U~1j6!) zH6adq0}#l{%*1U(Cb%4AJ}VLWKBPi0MoKFaQH6x?^hQ!6em@993xdtS%_dmevzeNl z(o?YlOI=jl(`L9^ z0O+H9k$_@`6L13eTT8ci-V0ljDMD|0ifUw|Q-Hep$xYj0hTO@0%IS^TD4b4n6EKDG z??uM;MEx`s98KYN(K0>c!C3HZdZ{+_53DO%9k5W%pr6yJusQAv_;IA}925Y%;+!tY z%2k!YQmLLOr{rF~!s<3-WEUs)`ix_mSU|cNRBIWxOox_Yb7Z=~Q45ZNe*u|m^|)d* zog=i>`=bTe!|;8F+#H>EjIMcgWcG2ORD`w0WD;YZAy5#s{65~qfI6o$+Ty&-hyMyJ z3Ra~t>R!p=5ZpxA;QkDAoPi4sYOP6>LT+}{xp}tk+<0k^CKCFdNYG(Es>p0gqD)jP zWOeX5G;9(m@?GOG7g;e74i_|SmE?`B2i;sLYwRWKLy0RLW!Hx`=!LH3&k=FuCsM=9M4|GqzA)anEHfxkB z?2iK-u(DC_T1};KaUT@3nP~LEcENT^UgPvp!QC@Dw&PVAhaEYrPey{nkcn(ro|r7XUz z%#(=$7D8uP_uU-oPHhd>>^adbCSQetgSG`e$U|7mr!`|bU0aHl_cmL)na-5x1#OsVE#m*+k84Y^+UMeSAa zbrVZHU=mFwXEaGHtXQq`2ZtjfS!B2H{5A<3(nb-6ARVV8kEmOkx6D2x7~-6hl;*-*}2Xz;J#a8Wn;_B5=m zl3dY;%krf?i-Ok^Pal-}4F`{F@TYPTwTEhxpZK5WCpfD^UmM_iYPe}wpE!Djai6_{ z*pGO=WB47#Xjb7!n2Ma)s^yeR*1rTxp`Mt4sfA+`HwZf%!7ZqGosPkw69`Ix5Ku6G z@Pa;pjzV&dn{M=QDx89t?p?d9gna*}jBly*#1!6}5K<*xDPJ{wv4& zM$17DFd~L*Te3A%yD;Dp9UGWTjRxAvMu!j^Tbc}2v~q^59d4bz zvu#!IJCy(BcWTc`;v$9tH;J%oiSJ_i7s;2`JXZF+qd4C)vY!hyCtl)sJIC{ebI*0> z@x>;EzyBv>AI-~{D6l6{ST=em*U( z(r$nuXY-#CCi^8Z2#v#UXOt`dbYN1z5jzNF2 z411?w)whZrfA20;nl&C1Gi+gk<`JSm+{|*2o<< zqM#@z_D`Cn|0H^9$|Tah)0M_X4c37|KQ*PmoT@%xHc3L1ZY6(p(sNXHa&49Frzto& zR`c~ClHpE~4Z=uKa5S(-?M8EJ$zt0&fJk~p$M#fGN1-y$7!37hld`Uw>Urri(DxLa;=#rK0g4J)pXMC zxzraOVw1+kNWpi#P=6(qxf`zSdUC?D$i`8ZI@F>k6k zz21?d+dw7b&i*>Kv5L(LH-?J%@WnqT7j#qZ9B>|Zl+=> z^U-pV@1y_ptHo4hl^cPRWewbLQ#g6XYQ@EkiP z;(=SU!yhjHp%1&MsU`FV1Z_#K1&(|5n(7IHbx&gG28HNT)*~-BQi372@|->2Aw5It z0CBpUcMA*QvsPy)#lr!lIdCi@1k4V2m!NH)%Px(vu-r(Q)HYc!p zJ^$|)j^E#q#QOgcb^pd74^JUi7fUmMiNP_o*lvx*q%_odv49Dsv$NV;6J z9GOXKomA{2Pb{w}&+yHtH?IkJJu~}Z?{Uk++2mB8zyvh*xhHKE``99>y#TdD z&(MH^^JHf;g(Tbb^&8P*;_i*2&fS$7${3WJtV7K&&(MBV2~)2KB3%cWg#1!VE~k#C z!;A;?p$s{ihyojEZz+$I1)L}&G~ml=udD9qh>Tu(ylv)?YcJT3ihapi!zgPtWb*CP zlLLJSRCj-^w?@;RU9aL2zDZY1`I3d<&OMuW=c3$o0#STpv_p3b9Wtbql>w^bBi~u4 z3D8KyF?YE?=HcKk!xcp@Cigvzy=lnFgc^9c%(^F22BWYNAYRSho@~*~S)4%AhEttv zvq>7X!!EWKG?mOd9&n>vvH1p4VzE?HCuxT-u+F&mnsfDI^}*-d00-KAauEaXqg3k@ zy#)MGX!X;&3&0s}F3q40ZmVM$(H3CLfpdL?hB6nVqMxX)q=1b}o_PG%r~hZ4gUfSp zOH4qlEOW4OMUc)_m)fMR_rl^pCfXc{$fQbI*E&mV77}kRF z&{<06AJyJ!e863o-V>FA1a9Eemx6>^F$~9ppt()ZbPGfg_NdRXBWoZnDy2;#ODgf! zgl?iOcF7Meo|{AF>KDwTgYrJLb$L2%%BEtO>T$C?|9bAB&}s;gI?lY#^tttY&hfr# zKhC+&b-rpg_?~uVK%S@mQleU#_xCsvIPK*<`E0fHE1&!J7!xD#IB|SSPW6-PyuqGn3^M^Rz%WT{e?OI^svARX&SAdU77V(C~ zM$H{Kg59op{<|8ry9ecfP%=kFm(-!W&?U0@<%z*+!*<e0XesMxRFu9QnGqun6R_%T+B%&9Dtk?*d$Q zb~>84jEAPi@&F@3wAa^Lzc(AJz5gsfZ7J53;@D<;Klpl?sK&u@gie`~vTsbOE~Cd4 z%kr56mI|#b(Jk&;p6plVwmNB0H@0SmgdmjIn5Ne@)}7Vty(yb2t3ev@22AE^s!KaN zyQ>j+F3w=wnx7w@FVCRe+`vUH)3gW%_72fxzqX!S&!dchdkRiHbXW1FMrIIBwjsai8`CB2r4mAbwp%rrO>3B$Zw;9=%fXI9B{d(UzVap7u z6piC-FQ)>}VOEuPpuqznpY`hN4dGa_1Xz9rVg(;H$5Te^F0dDv*gz9JS<|>>U0J^# z6)(4ICh+N_Q`Ft0hF|3fSHs*?a=XC;e`sJaU9&d>X4l?1W=|fr!5ShD|nv$GK;j46@BV6+{oRbWfqOBRb!ir88XD*SbC(LF}I1h#6@dvK%Toe%@ zhDyG$93H8Eu&gCYddP58iF3oQH*zLbNI;rN@E{T9%A8!=v#JLxKyUe}e}BJpB{~uN zqgxRgo0*-@-iaHPV8bTOH(rS(huwK1Xg0u+e!`(Irzu@Bld&s5&bWgVc@m7;JgELd zimVs`>vQ}B_1(2#rv#N9O`fJpVfPc7V2nv34PC);Dzbb;p!6pqHzvy?2pD&1NE)?A zt(t-ucqy@wn9`^MN5apa7K|L=9>ISC>xoc#>{@e}m#YAAa1*8-RUMKwbm|;5p>T`Z zNf*ph@tnF{gmDa3uwwN(g=`Rh)4!&)^oOy@VJaK4lMT&5#YbXkl`q?<*XtsqD z9PRK6bqb)fJw0g-^a@nu`^?71k|m3RPRjt;pIkCo1{*pdqbVs-Yl>4E>3fZx3Sv44grW=*qdSoiZ9?X0wWyO4`yDHh2E!9I!ZFi zVL8|VtW38}BOJHW(Ax#KL_KQzarbuE{(%TA)AY)@tY4%A%P%SqIU~8~-Lp3qY;U-} z`h_Gel7;K1h}7$_5ZZT0&%$Lxxr-<89V&&TCsu}LL#!xpQ1O31jaa{U34~^le*Y%L za?7$>Jk^k^pS^_M&cDs}NgXlR>16AHkSK-4TRaJSh#h&p!-!vQY%f+bmn6x`4fwTp z$727L^y`~!exvmE^W&#@uY!NxJi`g!i#(++!)?iJ(1)2Wk;RN zFK&O4eTkP$Xn~4bB|q8y(btx$R#D`O@epi4ofcETrx!IM(kWNEe42Qh(8*KqfP(c0 zouBl6>Fc_zM+V;F3znbo{x#%!?mH3`_ANJ?y7ppxS@glg#S9^MXu|FM&ynpz3o&Qh z2ujAHLF3($pH}0jXQsa#?t--TnF1P73b?4`KeJ9^qK-USHE)4!IYgMn-7z|=ALF5SNGkrtPG@Y~niUQV2?g$vzJN3nZ{7;HZHzWAeQ;5P|@Tl3YHpyznGG4-f4=XflwSJY+58-+wf?~Fg@1p1wkzuu-RF3j2JX37SQUc? zQ4v%`V8z9ZVZVqS8h|@@RpD?n0W<=hk=3Cf8R?d^9YK&e9ZybFY%jdnA)PeHvtBe- zhMLD+SSteHBq*q)d6x{)s1UrsO!byyLS$58WK;sqip$Mk{l)Y(_6hEIBsIjCr5t>( z7CdKUrJTrW%qZ#1z^n*Lb8#VdfzPw~OIL76aC+Rhr<~;4Tl!sw?Rj6hXj4XWa#6Tp z@)kJ~qOV)^Rh*-?aG>ic2*NlC2M7&LUzc9RT6WM%Cpe78`iAowe!>(T0jo&ivn8-7 zs{Qa@cGy$rE-3AY0V(l8wjI^uB8Lchj@?L}fYal^>T9z;8juH@?rG&g-t+R2dVDBe zq!K%{e-rT5jX19`(bP23LUN4+_zh2KD~EAYzhpEO3MUG8@}uBHH@4J zd`>_(K4q&>*k82(dDuC)X6JuPrBBubOg7qZ{?x!r@{%0);*`h*^F|%o?&1wX?Wr4b z1~&cy#PUuES{C#xJ84!z<1tp9sfrR(i%Tu^jnXy;4`Xk;AQCdFC@?V%|; zySdC7qS|uQRcH}EFZH%mMB~7gi}a0utE}ZE_}8PQH8f;H%PN41Cb9R%w5Oi5el^fd z$n{3SqLCnrF##x?4sa^r!O$7NX!}&}V;0ZGQ&K&i%6$3C_dR%I7%gdQ;KT6YZiQrW zk%q<74oVBV>@}CvJ4Wj!d^?#Zwq(b$E1ze4$99DuNg?6t9H}k_|D7KWD7i0-g*EO7 z;5{hSIYE4DMOK3H%|f5Edx+S0VI0Yw!tsaRS2&Il2)ea^8R5TG72BrJue|f_{2UHa z@w;^c|K3da#$TB0P3;MPlF7RuQeXT$ zS<<|C0OF(k)>fr&wOB=gP8!Qm>F41u;3esv7_0l%QHt(~+n; zf!G6%hp;Gfa9L9=AceiZs~tK+Tf*Wof=4!u{nIO90jH@iS0l+#%8=~%ASzFv7zqSB^?!@N7)kp0t&tCGLmzXSRMRyxCmCYUD2!B`? zhs$4%KO~m=VFk3Buv9osha{v+mAEq=ik3RdK@;WWTV_g&-$U4IM{1IhGX{pAu%Z&H zFfwCpUsX%RKg);B@7OUzZ{Hn{q6Vv!3#8fAg!P$IEx<0vAx;GU%}0{VIsmFBPq_mb zpe^BChDK>sc-WLKl<6 zwbW|e&d&dv9Wu0goueyu>(JyPx1mz0v4E?cJjFuKF71Q1)AL8jHO$!fYT3(;U3Re* zPPOe%*O+@JYt1bW`!W_1!mN&=w3G9ru1XsmwfS~BJ))PhD(+_J_^N6j)sx5VwbWK| zwRyC?W<`pOCY)b#AS?rluxuuGf-AJ=D!M36l{ua?@SJ5>e!IBr3CXIxWw5xUZ@Xrw z_R@%?{>d%Ld4p}nEsiA@v*nc6Ah!MUs?GA7e5Q5lPpp0@`%5xY$C;{%rz24$;vR#* zBP=a{)K#CwIY%p} zXVdxTQ^HS@O&~eIftU+Qt^~(DGxrdi3k}DdT^I7Iy5SMOp$QuD8s;+93YQ!OY{eB24%xY7ml@|M7I(Nb@K_-?F;2?et|CKkuZK_>+>Lvg!>JE~wN`BI|_h6$qi!P)+K-1Hh(1;a`os z55)4Q{oJiA(lQM#;w#Ta%T0jDNXIPM_bgESMCDEg6rM33anEr}=|Fn6)|jBP6Y}u{ zv9@%7*#RI9;fv;Yii5CI+KrRdr0DKh=L>)eO4q$1zmcSmglsV`*N(x=&Wx`*v!!hn6X-l0 zP_m;X??O(skcj+oS$cIdKhfT%ABAzz3w^la-Ucw?yBPEC+=Pe_vU8nd-HV5YX6X8r zZih&j^eLU=%*;VzhUyoLF;#8QsEfmByk+Y~caBqSvQaaWf2a{JKB9B>V&r?l^rXaC z8)6AdR@Qy_BxQrE2Fk?ewD!SwLuMj@&d_n5RZFf7=>O>hzVE*seW3U?_p|R^CfoY`?|#x9)-*yjv#lo&zP=uI`M?J zbzC<^3x7GfXA4{FZ72{PE*-mNHyy59Q;kYG@BB~NhTd6pm2Oj=_ zizmD?MKVRkT^KmXuhsk?eRQllPo2Ubk=uCKiZ&u3Xjj~<(!M94c)Tez@9M1Gfs5JV z->@II)CDJOXTtPrQudNjE}Eltbjq>6KiwAwqvAKd^|g!exgLG3;wP+#mZYr`cy3#39e653d=jrR-ulW|h#ddHu(m9mFoW~2yE zz5?dB%6vF}+`-&-W8vy^OCxm3_{02royjvmwjlp+eQDzFVEUiyO#gLv%QdDSI#3W* z?3!lL8clTaNo-DVJw@ynq?q!%6hTQi35&^>P85G$TqNt78%9_sSJt2RThO|JzM$iL zg|wjxdMC2|Icc5rX*qPL(coL!u>-xxz-rFiC!6hD1IR%|HSRsV3>Kq~&vJ=s3M5y8SG%YBQ|{^l#LGlg!D?E>2yR*eV%9m$_J6VGQ~AIh&P$_aFbh zULr0Z$QE!QpkP=aAeR4ny<#3Fwyw@rZf4?Ewq`;mCVv}xaz+3ni+}a=k~P+yaWt^L z@w67!DqVf7D%7XtXX5xBW;Co|HvQ8WR1k?r2cZD%U;2$bsM%u8{JUJ5Z0k= zZJARv^vFkmWx15CB=rb=D4${+#DVqy5$C%bf`!T0+epLJLnh1jwCdb*zuCL}eEFvE z{rO1%gxg>1!W(I!owu*mJZ0@6FM(?C+d*CeceZRW_4id*D9p5nzMY&{mWqrJomjIZ z97ZNnZ3_%Hx8dn;H>p8m7F#^2;T%yZ3H;a&N7tm=Lvs&lgJLW{V1@h&6Vy~!+Ffbb zv(n3+v)_D$}dqd!2>Y2B)#<+o}LH#%ogGi2-?xRIH)1!SD)u-L65B&bsJTC=LiaF+YOCif2dUX6uAA|#+vNR z>U+KQekVGon)Yi<93(d!(yw1h3&X0N(PxN2{%vn}cnV?rYw z$N^}_o!XUB!mckL`yO1rnUaI4wrOeQ(+&k?2mi47hzxSD`N#-byqd1IhEoh!PGq>t z_MRy{5B0eKY>;Ao3z$RUU7U+i?iX^&r739F)itdrTpAi-NN0=?^m%?{A9Ly2pVv>Lqs6moTP?T2-AHqFD-o_ znVr|7OAS#AEH}h8SRPQ@NGG47dO}l=t07__+iK8nHw^(AHx&Wb<%jPc$$jl6_p(b$ z)!pi(0fQodCHfM)KMEMUR&UID>}m^(!{C^U7sBDOA)$VThRCI0_+2=( zV8mMq0R(#z;C|7$m>$>`tX+T|xGt(+Y48@ZYu#z;0pCgYgmMVbFb!$?%yhZqP_nhn zy4<#3P1oQ#2b51NU1mGnHP$cf0j-YOgAA}A$QoL6JVLcmExs(kU{4z;PBHJD%_=0F z>+sQV`mzijSIT7xn%PiDKHOujX;n|M&qr1T@rOxTdxtZ!&u&3HHFLYD5$RLQ=heur zb>+AFokUVQeJy-#LP*^)spt{mb@Mqe=A~-4p0b+Bt|pZ+@CY+%x}9f}izU5;4&QFE zO1bhg&A4uC1)Zb67kuowWY4xbo&J=%yoXlFB)&$d*-}kjBu|w!^zbD1YPc0-#XTJr z)pm2RDy%J3jlqSMq|o%xGS$bPwn4AqitC6&e?pqWcjWPt{3I{>CBy;hg0Umh#c;hU3RhCUX=8aR>rmd` z7Orw(5tcM{|-^J?ZAA9KP|)X6n9$-kvr#j5YDecTM6n z&07(nD^qb8hpF0B^z^pQ*%5ePYkv&FabrlI61ntiVp!!C8y^}|<2xgAd#FY=8b*y( zuQOuvy2`Ii^`VBNJB&R!0{hABYX55ooCAJSSevl4RPqEGb)iy_0H}v@vFwFzD%>#I>)3PsouQ+_Kkbqy*kKdHdfkN7NBcq%V{x^fSxgXpg7$bF& zj!6AQbDY(1u#1_A#1UO9AxiZaCVN2F0wGXdY*g@x$ByvUA?ePdide0dmr#}udE%K| z3*k}Vv2Ew2u1FXBaVA6aerI36R&rzEZeDDCl5!t0J=ug6kuNZzH>3i_VN`%BsaVB3 zQYw|Xub_SGf{)F{$ZX5`Jc!X!;eybjP+o$I{Z^Hsj@D=E{MnnL+TbC@HEU2DjG{3-LDGIbq()U87x4eS;JXnSh;lRlJ z>EL3D>wHt-+wTjQF$fGyDO$>d+(fq@bPpLBS~xA~R=3JPbS{tzN(u~m#Po!?H;IYv zE;?8%^vle|%#oux(Lj!YzBKv+Fd}*Ur-dCBoX*t{KeNM*n~ZPYJ4NNKkI^MFbz9!v z4(Bvm*Kc!-$%VFEewYJKz-CQN{`2}KX4*CeJEs+Q(!kI%hN1!1P6iOq?ovz}X0IOi z)YfWpwW@pK08^69#wSyCZkX9?uZD?C^@rw^Y?gLS_xmFKkooyx$*^5#cPqntNTtSG zlP>XLMj2!VF^0k#ole7`-c~*~+_T5ls?x4)ah(j8vo_ zwb%S8qoaZqY0-$ZI+ViIA_1~~rAH7K_+yFS{0rT@eQtTAdz#8E5VpwnW!zJ_^{Utv zlW5Iar3V5t&H4D6A=>?mq;G92;1cg9a2sf;gY9pJDVKn$DYdQlvfXq}zz8#LyPGq@ z+`YUMD;^-6w&r-82JL7mA8&M~Pj@aK!m{0+^v<|t%APYf7`}jGEhdYLqsHW-Le9TL z_hZZ1gbrz7$f9^fAzVIP30^KIz!!#+DRLL+qMszvI_BpOSmjtl$hh;&UeM{ER@INV zcI}VbiVTPoN|iSna@=7XkP&-4#06C};8ajbxJ4Gcq8(vWv4*&X8bM^T$mBk75Q92j z1v&%a;OSKc8EIrodmIiw$lOES2hzGDcjjB`kEDfJe{r}yE6`eZL zEB`9u>Cl0IsQ+t}`-cx}{6jqcANucqIB>Qmga_&<+80E2Q|VHHQ$YlAt{6`Qu`HA3 z03s0-sSlwbvgi&_R8s={6<~M^pGvBNjKOa>tWenzS8s zR>L7R5aZ=mSU{f?ib4Grx$AeFvtO5N|D>9#)ChH#Fny2maHWHOf2G=#<9Myot#+4u zWVa6d^Vseq_0=#AYS(-m$Lp;*8nC_6jXIjEM`omUmtH@QDs3|G)i4j*#_?#UYVZvJ z?YjT-?!4Q{BNun;dKBWLEw2C-VeAz`%?A>p;)PL}TAZn5j~HK>v1W&anteARlE+~+ zj>c(F;?qO3pXBb|#OZdQnm<4xWmn~;DR5SDMxt0UK_F^&eD|KZ=O;tO3vy4@4h^;2 zUL~-z`-P1aOe?|ZC1BgVsL)2^J-&vIFI%q@40w0{jjEfeVl)i9(~bt2z#2Vm)p`V_ z1;6$Ae7=YXk#=Qkd24Y23t&GvRxaOoad~NbJ+6pxqzJ>FY#Td7@`N5xp!n(c!=RE& z&<<@^a$_Ys8jqz4|5Nk#FY$~|FPC0`*a5HH!|Gssa9=~66&xG9)|=pOOJ2KE5|YrR zw!w6K2aC=J$t?L-;}5hn6mHd%hC;p8P|Dgh6D>hGnXPgi;6r+eA=?f72y9(Cf_ho{ zH6#)uD&R=73^$$NE;5piWX2bzR67fQ)`b=85o0eOLGI4c-Tb@-KNi2pz=Ke@SDcPn za$AxXib84`!Sf;Z3B@TSo`Dz7GM5Kf(@PR>Ghzi=BBxK8wRp>YQoXm+iL>H*Jo9M3 z6w&E?BC8AFTFT&Tv8zf+m9<&S&%dIaZ)Aoqkak_$r-2{$d~0g2oLETx9Y`eOAf14QXEQw3tJne;fdzl@wV#TFXSLXM2428F-Q}t+n2g%vPRMUzYPvzQ9f# zu(liiJem9P*?0%V@RwA7F53r~|I!Ty)<*AsMX3J{_4&}{6pT%Tpw>)^|DJ)>gpS~1rNEh z0$D?uO8mG?H;2BwM5a*26^7YO$XjUm40XmBsb63MoR;bJh63J;OngS5sSI+o2HA;W zdZV#8pDpC9Oez&L8loZO)MClRz!_!WD&QRtQxnazhT%Vj6Wl4G11nUk8*vSeVab@N#oJ}`KyJv+8Mo@T1-pqZ1t|?cnaVOd;1(h9 z!$DrN=jcGsVYE-0-n?oCJ^4x)F}E;UaD-LZUIzcD?W^ficqJWM%QLy6QikrM1aKZC zi{?;oKwq^Vsr|&`i{jIphA8S6G4)$KGvpULjH%9u(Dq247;R#l&I0{IhcC|oBF*Al zvLo7Xte=C{aIt*otJD}BUq)|_pdR>{zBMT< z(^1RpZv*l*m*OV^8>9&asGBo8h*_4q*)-eCv*|Pq=XNGrZE)^(SF7^{QE_~4VDB(o zVcPA_!G+2CAtLbl+`=Q~9iW`4ZRLku!uB?;tWqVjB0lEOf}2RD7dJ=BExy=<9wkb- z9&7{XFA%n#JsHYN8t5d~=T~5DcW4$B%3M+nNvC2`0!#@sckqlzo5;hhGi(D9=*A4` z5ynobawSPRtWn&CDLEs3Xf`(8^zDP=NdF~F^s&={l7(aw&EG}KWpMjtmz7j_VLO;@ zM2NVLDxZ@GIv7*gzl1 zjq78tv*8#WSY`}Su0&C;2F$Ze(q>F(@Wm^Gw!)(j;dk9Ad{STaxn)IV9FZhm*n+U} zi;4y*3v%A`_c7a__DJ8D1b@dl0Std3F||4Wtvi)fCcBRh!X9$1x!_VzUh>*S5s!oq z;qd{J_r79EL2wIeiGAqFstWtkfIJpjVh%zFo*=55B9Zq~y0=^iqHWfQl@O!Ak;(o*m!pZqe9 z%U2oDOhR)BvW8&F70L;2TpkzIutIvNQaTjjs5V#8mV4!NQ}zN=i`i@WI1z0eN-iCS z;vL-Wxc^Vc_qK<5RPh(}*8dLT{~GzE{w2o$2kMFaEl&q zP{V=>&3kW7tWaK-Exy{~`v4J0U#OZBk{a9{&)&QG18L@6=bsZ1zC_d{{pKZ-Ey>I> z;8H0t4bwyQqgu4hmO`3|4K{R*5>qnQ&gOfdy?z`XD%e5+pTDzUt3`k^u~SaL&XMe= z9*h#kT(*Q9jO#w2Hd|Mr-%DV8i_1{J1MU~XJ3!WUplhXDYBpJH><0OU`**nIvPIof z|N8@I=wA)sf45SAvx||f?Z5uB$kz1qL3Ky_{%RPdP5iN-D2!p5scq}buuC00C@jom zhfGKm3|f?Z0iQ|K$Z~!`8{nmAS1r+fp6r#YDOS8V*;K&Gs7Lc&f^$RC66O|)28oh`NHy&vq zJh+hAw8+ybTB0@VhWN^0iiTnLsCWbS_y`^gs!LX!Lw{yE``!UVzrV24tP8o;I6-65 z1MUiHw^{bB15tmrVT*7-#sj6cs~z`wk52YQJ*TG{SE;KTm#Hf#a~|<(|ImHH17nNM z`Ub{+J3dMD!)mzC8b(2tZtokKW5pAwHa?NFiso~# z1*iaNh4lQ4TS)|@G)H4dZV@l*Vd;Rw;-;odDhW2&lJ%m@jz+Panv7LQm~2Js6rOW3 z0_&2cW^b^MYW3)@o;neZ<{B4c#m48dAl$GCc=$>ErDe|?y@z`$uq3xd(%aAsX)D%l z>y*SQ%My`yDP*zof|3@_w#cjaW_YW4BdA;#Glg1RQcJGY*CJ9`H{@|D+*e~*457kd z73p<%fB^PV!Ybw@)Dr%(ZJbX}xmCStCYv#K3O32ej{$9IzM^I{6FJ8!(=azt7RWf4 z7ib0UOPqN40X!wOnFOoddd8`!_IN~9O)#HRTyjfc#&MCZ zZAMzOVB=;qwt8gV?{Y2?b=iSZG~RF~uyx18K)IDFLl})G1v@$(s{O4@RJ%OTJyF+Cpcx4jmy|F3euCnMK!P2WTDu5j z{{gD$=M*pH!GGzL%P)V2*ROm>!$Y=z|D`!_yY6e7SU$~a5q8?hZGgaYqaiLnkK%?0 zs#oI%;zOxF@g*@(V4p!$7dS1rOr6GVs6uYCTt2h)eB4?(&w8{#o)s#%gN@BBosRUe z)@P@8_Zm89pr~)b>e{tbPC~&_MR--iB{=)y;INU5#)@Gix-YpgP<-c2Ms{9zuCX|3 z!p(?VaXww&(w&uBHzoT%!A2=3HAP>SDxcljrego7rY|%hxy3XlODWffO_%g|l+7Y_ zqV(xbu)s4lV=l7M;f>vJl{`6qBm>#ZeMA}kXb97Z)?R97EkoI?x6Lp0yu1Z>PS?2{ z0QQ(8D)|lc9CO3B~e(pQM&5(1y&y=e>C^X$`)_&XuaI!IgDTVqt31wX#n+@!a_A0ZQkA zCJ2@M_4Gb5MfCrm5UPggeyh)8 zO9?`B0J#rkoCx(R0I!ko_2?iO@|oRf1;3r+i)w-2&j?=;NVIdPFsB)`|IC0zk6r9c zRrkfxWsiJ(#8QndNJj@{@WP2Ackr|r1VxV{7S&rSU(^)-M8gV>@UzOLXu9K<{6e{T zXJ6b92r$!|lwjhmgqkdswY&}c)KW4A)-ac%sU;2^fvq7gfUW4Bw$b!i@duy1CAxSn z(pyh$^Z=&O-q<{bZUP+$U}=*#M9uVc>CQVgDs4swy5&8RAHZ~$)hrTF4W zPsSa~qYv_0mJnF89RnnJTH`3}w4?~epFl=D(35$ zWa07ON$`OMBOHgCmfO(9RFc<)?$x)N}Jd2A(<*Ll7+4jrRt9w zwGxExUXd9VB#I|DwfxvJ;HZ8Q{37^wDhaZ%O!oO(HpcqfLH%#a#!~;Jl7F5>EX_=8 z{()l2NqPz>La3qJR;_v+wlK>GsHl;uRA8%j`A|yH@k5r%55S9{*Cp%uw6t`qc1!*T za2OeqtQj7sAp#Q~=5Fs&aCR9v>5V+s&RdNvo&H~6FJOjvaj--2sYYBvMq;55%z8^o z|BJDA4vzfow#DO#ZQHh;Oq_{r+qP{R9ox2TOgwQiv7Ow!zjN+A@BN;0tA2lUb#+zO z(^b89eV)D7UVE+h{mcNc6&GtpOqDn_?VAQ)Vob$hlFwW%xh>D#wml{t&Ofmm_d_+; zKDxzdr}`n2Rw`DtyIjrG)eD0vut$}dJAZ0AohZ+ZQdWXn_Z@dI_y=7t3q8x#pDI-K z2VVc&EGq445Rq-j0=U=Zx`oBaBjsefY;%)Co>J3v4l8V(T8H?49_@;K6q#r~Wwppc z4XW0(4k}cP=5ex>-Xt3oATZ~bBWKv)aw|I|Lx=9C1s~&b77idz({&q3T(Y(KbWO?+ zmcZ6?WeUsGk6>km*~234YC+2e6Zxdl~<_g2J|IE`GH%n<%PRv-50; zH{tnVts*S5*_RxFT9eM0z-pksIb^drUq4>QSww=u;UFCv2AhOuXE*V4z?MM`|ABOC4P;OfhS(M{1|c%QZ=!%rQTDFx`+}?Kdx$&FU?Y<$x;j7z=(;Lyz+?EE>ov!8vvMtSzG!nMie zsBa9t8as#2nH}n8xzN%W%U$#MHNXmDUVr@GX{?(=yI=4vks|V)!-W5jHsU|h_&+kY zS_8^kd3jlYqOoiI`ZqBVY!(UfnAGny!FowZWY_@YR0z!nG7m{{)4OS$q&YDyw6vC$ zm4!$h>*|!2LbMbxS+VM6&DIrL*X4DeMO!@#EzMVfr)e4Tagn~AQHIU8?e61TuhcKD zr!F4(kEebk(Wdk-?4oXM(rJwanS>Jc%<>R(siF+>+5*CqJLecP_we33iTFTXr6W^G z7M?LPC-qFHK;E!fxCP)`8rkxZyFk{EV;G-|kwf4b$c1k0atD?85+|4V%YATWMG|?K zLyLrws36p%Qz6{}>7b>)$pe>mR+=IWuGrX{3ZPZXF3plvuv5Huax86}KX*lbPVr}L z{C#lDjdDeHr~?l|)Vp_}T|%$qF&q#U;ClHEPVuS+Jg~NjC1RP=17=aQKGOcJ6B3mp z8?4*-fAD~}sX*=E6!}^u8)+m2j<&FSW%pYr_d|p_{28DZ#Cz0@NF=gC-o$MY?8Ca8 zr5Y8DSR^*urS~rhpX^05r30Ik#2>*dIOGxRm0#0YX@YQ%Mg5b6dXlS!4{7O_kdaW8PFSdj1=ryI-=5$fiieGK{LZ+SX(1b=MNL!q#lN zv98?fqqTUH8r8C7v(cx#BQ5P9W>- zmW93;eH6T`vuJ~rqtIBg%A6>q>gnWb3X!r0wh_q;211+Om&?nvYzL1hhtjB zK_7G3!n7PL>d!kj){HQE zE8(%J%dWLh1_k%gVXTZt zEdT09XSKAx27Ncaq|(vzL3gm83q>6CAw<$fTnMU05*xAe&rDfCiu`u^1)CD<>sx0i z*hr^N_TeN89G(nunZoLBf^81#pmM}>JgD@Nn1l*lN#a=B=9pN%tmvYFjFIoKe_(GF z-26x{(KXdfsQL7Uv6UtDuYwV`;8V3w>oT_I<`Ccz3QqK9tYT5ZQzbop{=I=!pMOCb zCU68`n?^DT%^&m>A%+-~#lvF!7`L7a{z<3JqIlk1$<||_J}vW1U9Y&eX<}l8##6i( zZcTT@2`9(Mecptm@{3A_Y(X`w9K0EwtPq~O!16bq{7c0f7#(3wn-^)h zxV&M~iiF!{-6A@>o;$RzQ5A50kxXYj!tcgme=Qjrbje~;5X2xryU;vH|6bE(8z^<7 zQ>BG7_c*JG8~K7Oe68i#0~C$v?-t@~@r3t2inUnLT(c=URpA9kA8uq9PKU(Ps(LVH zqgcqW>Gm?6oV#AldDPKVRcEyQIdTT`Qa1j~vS{<;SwyTdr&3*t?J)y=M7q*CzucZ&B0M=joT zBbj@*SY;o2^_h*>R0e({!QHF0=)0hOj^B^d*m>SnRrwq>MolNSgl^~r8GR#mDWGYEIJA8B<|{{j?-7p zVnV$zancW3&JVDtVpIlI|5djKq0(w$KxEFzEiiL=h5Jw~4Le23@s(mYyXWL9SX6Ot zmb)sZaly_P%BeX_9 zw&{yBef8tFm+%=--m*J|o~+Xg3N+$IH)t)=fqD+|fEk4AAZ&!wcN5=mi~Vvo^i`}> z#_3ahR}Ju)(Px7kev#JGcSwPXJ2id9%Qd2A#Uc@t8~egZ8;iC{e! z%=CGJOD1}j!HW_sgbi_8suYnn4#Ou}%9u)dXd3huFIb!ytlX>Denx@pCS-Nj$`VO&j@(z!kKSP0hE4;YIP#w9ta=3DO$7f*x zc9M4&NK%IrVmZAe=r@skWD`AEWH=g+r|*13Ss$+{c_R!b?>?UaGXlw*8qDmY#xlR= z<0XFbs2t?8i^G~m?b|!Hal^ZjRjt<@a? z%({Gn14b4-a|#uY^=@iiKH+k?~~wTj5K1A&hU z2^9-HTC)7zpoWK|$JXaBL6C z#qSNYtY>65T@Zs&-0cHeu|RX(Pxz6vTITdzJdYippF zC-EB+n4}#lM7`2Ry~SO>FxhKboIAF#Z{1wqxaCb{#yEFhLuX;Rx(Lz%T`Xo1+a2M}7D+@wol2)OJs$TwtRNJ={( zD@#zTUEE}#Fz#&(EoD|SV#bayvr&E0vzmb%H?o~46|FAcx?r4$N z&67W3mdip-T1RIxwSm_&(%U|+WvtGBj*}t69XVd&ebn>KOuL(7Y8cV?THd-(+9>G7*Nt%T zcH;`p={`SOjaf7hNd(=37Lz3-51;58JffzIPgGs_7xIOsB5p2t&@v1mKS$2D$*GQ6 zM(IR*j4{nri7NMK9xlDy-hJW6sW|ZiDRaFiayj%;(%51DN!ZCCCXz+0Vm#};70nOx zJ#yA0P3p^1DED;jGdPbQWo0WATN=&2(QybbVdhd=Vq*liDk`c7iZ?*AKEYC#SY&2g z&Q(Ci)MJ{mEat$ZdSwTjf6h~roanYh2?9j$CF@4hjj_f35kTKuGHvIs9}Re@iKMxS-OI*`0S z6s)fOtz}O$T?PLFVSeOjSO26$@u`e<>k(OSP!&YstH3ANh>)mzmKGNOwOawq-MPXe zy4xbeUAl6tamnx))-`Gi2uV5>9n(73yS)Ukma4*7fI8PaEwa)dWHs6QA6>$}7?(L8 ztN8M}?{Tf!Zu22J5?2@95&rQ|F7=FK-hihT-vDp!5JCcWrVogEnp;CHenAZ)+E+K5 z$Cffk5sNwD_?4+ymgcHR(5xgt20Z8M`2*;MzOM#>yhk{r3x=EyM226wb&!+j`W<%* zSc&|`8!>dn9D@!pYow~(DsY_naSx7(Z4i>cu#hA5=;IuI88}7f%)bRkuY2B;+9Uep zpXcvFWkJ!mQai63BgNXG26$5kyhZ2&*3Q_tk)Ii4M>@p~_~q_cE!|^A;_MHB;7s#9 zKzMzK{lIxotjc};k67^Xsl-gS!^*m*m6kn|sbdun`O?dUkJ{0cmI0-_2y=lTAfn*Y zKg*A-2sJq)CCJgY0LF-VQvl&6HIXZyxo2#!O&6fOhbHXC?%1cMc6y^*dOS{f$=137Ds1m01qs`>iUQ49JijsaQ( zksqV9@&?il$|4Ua%4!O15>Zy&%gBY&wgqB>XA3!EldQ%1CRSM(pp#k~-pkcCg4LAT zXE=puHbgsw)!xtc@P4r~Z}nTF=D2~j(6D%gTBw$(`Fc=OOQ0kiW$_RDd=hcO0t97h zb86S5r=>(@VGy1&#S$Kg_H@7G^;8Ue)X5Y+IWUi`o;mpvoV)`fcVk4FpcT|;EG!;? zHG^zrVVZOm>1KFaHlaogcWj(v!S)O(Aa|Vo?S|P z5|6b{qkH(USa*Z7-y_Uvty_Z1|B{rTS^qmEMLEYUSk03_Fg&!O3BMo{b^*`3SHvl0 zhnLTe^_vVIdcSHe)SQE}r~2dq)VZJ!aSKR?RS<(9lzkYo&dQ?mubnWmgMM37Nudwo z3Vz@R{=m2gENUE3V4NbIzAA$H1z0pagz94-PTJyX{b$yndsdKptmlKQKaaHj@3=ED zc7L?p@%ui|RegVYutK$64q4pe9+5sv34QUpo)u{1ci?)_7gXQd{PL>b0l(LI#rJmN zGuO+%GO`xneFOOr4EU(Wg}_%bhzUf;d@TU+V*2#}!2OLwg~%D;1FAu=Un>OgjPb3S z7l(riiCwgghC=Lm5hWGf5NdGp#01xQ59`HJcLXbUR3&n%P(+W2q$h2Qd z*6+-QXJ*&Kvk9ht0f0*rO_|FMBALen{j7T1l%=Q>gf#kma zQlg#I9+HB+z*5BMxdesMND`_W;q5|FaEURFk|~&{@qY32N$G$2B=&Po{=!)x5b!#n zxLzblkq{yj05#O7(GRuT39(06FJlalyv<#K4m}+vs>9@q-&31@1(QBv82{}Zkns~K ze{eHC_RDX0#^A*JQTwF`a=IkE6Ze@j#-8Q`tTT?k9`^ZhA~3eCZJ-Jr{~7Cx;H4A3 zcZ+Zj{mzFZbVvQ6U~n>$U2ZotGsERZ@}VKrgGh0xM;Jzt29%TX6_&CWzg+YYMozrM z`nutuS)_0dCM8UVaKRj804J4i%z2BA_8A4OJRQ$N(P9Mfn-gF;4#q788C@9XR0O3< zsoS4wIoyt046d+LnSCJOy@B@Uz*#GGd#+Ln1ek5Dv>(ZtD@tgZlPnZZJGBLr^JK+!$$?A_fA3LOrkoDRH&l7 zcMcD$Hsjko3`-{bn)jPL6E9Ds{WskMrivsUu5apD z?grQO@W7i5+%X&E&p|RBaEZ(sGLR@~(y^BI@lDMot^Ll?!`90KT!JXUhYS`ZgX3jnu@Ja^seA*M5R@f`=`ynQV4rc$uT1mvE?@tz)TN<=&H1%Z?5yjxcpO+6y_R z6EPuPKM5uxKpmZfT(WKjRRNHs@ib)F5WAP7QCADvmCSD#hPz$V10wiD&{NXyEwx5S z6NE`3z!IS^$s7m}PCwQutVQ#~w+V z=+~->DI*bR2j0^@dMr9`p>q^Ny~NrAVxrJtX2DUveic5vM%#N*XO|?YAWwNI$Q)_) zvE|L(L1jP@F%gOGtnlXtIv2&1i8q<)Xfz8O3G^Ea~e*HJsQgBxWL(yuLY+jqUK zRE~`-zklrGog(X}$9@ZVUw!8*=l`6mzYLtsg`AvBYz(cxmAhr^j0~(rzXdiOEeu_p zE$sf2(w(BPAvO5DlaN&uQ$4@p-b?fRs}d7&2UQ4Fh?1Hzu*YVjcndqJLw0#q@fR4u zJCJ}>_7-|QbvOfylj+e^_L`5Ep9gqd>XI3-O?Wp z-gt*P29f$Tx(mtS`0d05nHH=gm~Po_^OxxUwV294BDKT>PHVlC5bndncxGR!n(OOm znsNt@Q&N{TLrmsoKFw0&_M9$&+C24`sIXGWgQaz=kY;S{?w`z^Q0JXXBKFLj0w0U6P*+jPKyZHX9F#b0D1$&(- zrm8PJd?+SrVf^JlfTM^qGDK&-p2Kdfg?f>^%>1n8bu&byH(huaocL>l@f%c*QkX2i znl}VZ4R1en4S&Bcqw?$=Zi7ohqB$Jw9x`aM#>pHc0x z0$!q7iFu zZ`tryM70qBI6JWWTF9EjgG@>6SRzsd}3h+4D8d~@CR07P$LJ}MFsYi-*O%XVvD@yT|rJ+Mk zDllJ7$n0V&A!0flbOf)HE6P_afPWZmbhpliqJuw=-h+r;WGk|ntkWN(8tKlYpq5Ow z(@%s>IN8nHRaYb*^d;M(D$zGCv5C|uqmsDjwy4g=Lz>*OhO3z=)VD}C<65;`89Ye} zSCxrv#ILzIpEx1KdLPlM&%Cctf@FqTKvNPXC&`*H9=l=D3r!GLM?UV zOxa(8ZsB`&+76S-_xuj?G#wXBfDY@Z_tMpXJS7^mp z@YX&u0jYw2A+Z+bD#6sgVK5ZgdPSJV3>{K^4~%HV?rn~4D)*2H!67Y>0aOmzup`{D zzDp3c9yEbGCY$U<8biJ_gB*`jluz1ShUd!QUIQJ$*1;MXCMApJ^m*Fiv88RZ zFopLViw}{$Tyhh_{MLGIE2~sZ)t0VvoW%=8qKZ>h=adTe3QM$&$PO2lfqH@brt!9j ziePM8$!CgE9iz6B<6_wyTQj?qYa;eC^{x_0wuwV~W+^fZmFco-o%wsKSnjXFEx02V zF5C2t)T6Gw$Kf^_c;Ei3G~uC8SM-xyycmXyC2hAVi-IfXqhu$$-C=*|X?R0~hu z8`J6TdgflslhrmDZq1f?GXF7*ALeMmOEpRDg(s*H`4>_NAr`2uqF;k;JQ+8>A|_6ZNsNLECC%NNEb1Y1dP zbIEmNpK)#XagtL4R6BC{C5T(+=yA-(Z|Ap}U-AfZM#gwVpus3(gPn}Q$CExObJ5AC z)ff9Yk?wZ}dZ-^)?cbb9Fw#EjqQ8jxF4G3=L?Ra zg_)0QDMV1y^A^>HRI$x?Op@t;oj&H@1xt4SZ9(kifQ zb59B*`M99Td7@aZ3UWvj1rD0sE)d=BsBuW*KwkCds7ay(7*01_+L}b~7)VHI>F_!{ zyxg-&nCO?v#KOUec0{OOKy+sjWA;8rTE|Lv6I9H?CI?H(mUm8VXGwU$49LGpz&{nQp2}dinE1@lZ1iox6{ghN&v^GZv9J${7WaXj)<0S4g_uiJ&JCZ zr8-hsu`U%N;+9N^@&Q0^kVPB3)wY(rr}p7{p0qFHb3NUUHJb672+wRZs`gd1UjKPX z4o6zljKKA+Kkj?H>Ew63o%QjyBk&1!P22;MkD>sM0=z_s-G{mTixJCT9@_|*(p^bz zJ8?ZZ&;pzV+7#6Mn`_U-)k8Pjg?a;|Oe^us^PoPY$Va~yi8|?+&=y$f+lABT<*pZr zP}D{~Pq1Qyni+@|aP;ixO~mbEW9#c0OU#YbDZIaw=_&$K%Ep2f%hO^&P67hApZe`x zv8b`Mz@?M_7-)b!lkQKk)JXXUuT|B8kJlvqRmRpxtQDgvrHMXC1B$M@Y%Me!BSx3P z#2Eawl$HleZhhTS6Txm>lN_+I`>eV$&v9fOg)%zVn3O5mI*lAl>QcHuW6!Kixmq`X zBCZ*Ck6OYtDiK!N47>jxI&O2a9x7M|i^IagRr-fmrmikEQGgw%J7bO|)*$2FW95O4 zeBs>KR)izRG1gRVL;F*sr8A}aRHO0gc$$j&ds8CIO1=Gwq1%_~E)CWNn9pCtBE}+`Jelk4{>S)M)`Ll=!~gnn1yq^EX(+y*ik@3Ou0qU`IgYi3*doM+5&dU!cho$pZ zn%lhKeZkS72P?Cf68<#kll_6OAO26bIbueZx**j6o;I0cS^XiL`y+>{cD}gd%lux} z)3N>MaE24WBZ}s0ApfdM;5J_Ny}rfUyxfkC``Awo2#sgLnGPewK};dORuT?@I6(5~ z?kE)Qh$L&fwJXzK){iYx!l5$Tt|^D~MkGZPA}(o6f7w~O2G6Vvzdo*a;iXzk$B66$ zwF#;wM7A+(;uFG4+UAY(2`*3XXx|V$K8AYu#ECJYSl@S=uZW$ksfC$~qrrbQj4??z-)uz0QL}>k^?fPnJTPw% zGz)~?B4}u0CzOf@l^um}HZzbaIwPmb<)< zi_3@E9lc)Qe2_`*Z^HH;1CXOceL=CHpHS{HySy3T%<^NrWQ}G0i4e1xm_K3(+~oi$ zoHl9wzb?Z4j#90DtURtjtgvi7uw8DzHYmtPb;?%8vb9n@bszT=1qr)V_>R%s!92_` zfnHQPANx z<#hIjIMm#*(v*!OXtF+w8kLu`o?VZ5k7{`vw{Yc^qYclpUGIM_PBN1+c{#Vxv&E*@ zxg=W2W~JuV{IuRYw3>LSI1)a!thID@R=bU+cU@DbR^_SXY`MC7HOsCN z!dO4OKV7(E_Z8T#8MA1H`99?Z!r0)qKW_#|29X3#Jb+5+>qUidbeP1NJ@)(qi2S-X zao|f0_tl(O+$R|Qwd$H{_ig|~I1fbp_$NkI!0E;Y z6JrnU{1Ra6^on{9gUUB0mwzP3S%B#h0fjo>JvV~#+X0P~JV=IG=yHG$O+p5O3NUgG zEQ}z6BTp^Fie)Sg<){Z&I8NwPR(=mO4joTLHkJ>|Tnk23E(Bo`FSbPc05lF2-+)X? z6vV3*m~IBHTy*^E!<0nA(tCOJW2G4DsH7)BxLV8kICn5lu6@U*R`w)o9;Ro$i8=Q^V%uH8n3q=+Yf;SFRZu z!+F&PKcH#8cG?aSK_Tl@K9P#8o+jry@gdexz&d(Q=47<7nw@e@FFfIRNL9^)1i@;A z28+$Z#rjv-wj#heI|<&J_DiJ*s}xd-f!{J8jfqOHE`TiHHZVIA8CjkNQ_u;Ery^^t zl1I75&u^`1_q)crO+JT4rx|z2ToSC>)Or@-D zy3S>jW*sNIZR-EBsfyaJ+Jq4BQE4?SePtD2+jY8*%FsSLZ9MY>+wk?}}}AFAw)vr{ml)8LUG-y9>^t!{~|sgpxYc0Gnkg`&~R z-pilJZjr@y5$>B=VMdZ73svct%##v%wdX~9fz6i3Q-zOKJ9wso+h?VME7}SjL=!NUG{J?M&i!>ma`eoEa@IX`5G>B1(7;%}M*%-# zfhJ(W{y;>MRz!Ic8=S}VaBKqh;~7KdnGEHxcL$kA-6E~=!hrN*zw9N+_=odt<$_H_8dbo;0=42wcAETPCVGUr~v(`Uai zb{=D!Qc!dOEU6v)2eHSZq%5iqK?B(JlCq%T6av$Cb4Rko6onlG&?CqaX7Y_C_cOC3 zYZ;_oI(}=>_07}Oep&Ws7x7-R)cc8zfe!SYxJYP``pi$FDS)4Fvw5HH=FiU6xfVqIM!hJ;Rx8c0cB7~aPtNH(Nmm5Vh{ibAoU#J6 zImRCr?(iyu_4W_6AWo3*vxTPUw@vPwy@E0`(>1Qi=%>5eSIrp^`` zK*Y?fK_6F1W>-7UsB)RPC4>>Ps9)f+^MqM}8AUm@tZ->j%&h1M8s*s!LX5&WxQcAh z8mciQej@RPm?660%>{_D+7er>%zX_{s|$Z+;G7_sfNfBgY(zLB4Ey}J9F>zX#K0f6 z?dVNIeEh?EIShmP6>M+d|0wMM85Sa4diw1hrg|ITJ}JDg@o8y>(rF9mXk5M z2@D|NA)-7>wD&wF;S_$KS=eE84`BGw3g0?6wGxu8ys4rwI?9U=*^VF22t3%mbGeOh z`!O-OpF7#Vceu~F`${bW0nYVU9ecmk31V{tF%iv&5hWofC>I~cqAt@u6|R+|HLMMX zVxuSlMFOK_EQ86#E8&KwxIr8S9tj_goWtLv4f@!&h8;Ov41{J~496vp9vX=(LK#j! zAwi*21RAV-LD>9Cw3bV_9X(X3)Kr0-UaB*7Y>t82EQ%!)(&(XuAYtTsYy-dz+w=$ir)VJpe!_$ z6SGpX^i(af3{o=VlFPC);|J8#(=_8#vdxDe|Cok+ANhYwbE*FO`Su2m1~w+&9<_9~ z-|tTU_ACGN`~CNW5WYYBn^B#SwZ(t4%3aPp z;o)|L6Rk569KGxFLUPx@!6OOa+5OjQLK5w&nAmwxkC5rZ|m&HT8G%GVZxB_@ME z>>{rnXUqyiJrT(8GMj_ap#yN_!9-lO5e8mR3cJiK3NE{_UM&=*vIU`YkiL$1%kf+1 z4=jk@7EEj`u(jy$HnzE33ZVW_J4bj}K;vT?T91YlO(|Y0FU4r+VdbmQ97%(J5 zkK*Bed8+C}FcZ@HIgdCMioV%A<*4pw_n}l*{Cr4}a(lq|injK#O?$tyvyE`S%(1`H z_wwRvk#13ElkZvij2MFGOj`fhy?nC^8`Zyo%yVcUAfEr8x&J#A{|moUBAV_^f$hpaUuyQeY3da^ zS9iRgf87YBwfe}>BO+T&Fl%rfpZh#+AM?Dq-k$Bq`vG6G_b4z%Kbd&v>qFjow*mBl z-OylnqOpLg}or7_VNwRg2za3VBK6FUfFX{|TD z`Wt0Vm2H$vdlRWYQJqDmM?JUbVqL*ZQY|5&sY*?!&%P8qhA~5+Af<{MaGo(dl&C5t zE%t!J0 zh6jqANt4ABdPxSTrVV}fLsRQal*)l&_*rFq(Ez}ClEH6LHv{J#v?+H-BZ2)Wy{K@9 z+ovXHq~DiDvm>O~r$LJo!cOuwL+Oa--6;UFE2q@g3N8Qkw5E>ytz^(&($!O47+i~$ zKM+tkAd-RbmP{s_rh+ugTD;lriL~`Xwkad#;_aM?nQ7L_muEFI}U_4$phjvYgleK~`Fo`;GiC07&Hq1F<%p;9Q;tv5b?*QnR%8DYJH3P>Svmv47Y>*LPZJy8_{9H`g6kQpyZU{oJ`m%&p~D=K#KpfoJ@ zn-3cqmHsdtN!f?~w+(t+I`*7GQA#EQC^lUA9(i6=i1PqSAc|ha91I%X&nXzjYaM{8$s&wEx@aVkQ6M{E2 zfzId#&r(XwUNtPcq4Ngze^+XaJA1EK-%&C9j>^9(secqe{}z>hR5CFNveMsVA)m#S zk)_%SidkY-XmMWlVnQ(mNJ>)ooszQ#vaK;!rPmGKXV7am^_F!Lz>;~{VrIO$;!#30XRhE1QqO_~#+Ux;B_D{Nk=grn z8Y0oR^4RqtcYM)7a%@B(XdbZCOqnX#fD{BQTeLvRHd(irHKq=4*jq34`6@VAQR8WG z^%)@5CXnD_T#f%@-l${>y$tfb>2LPmc{~5A82|16mH)R?&r#KKLs7xpN-D`=&Cm^R zvMA6#Ahr<3X>Q7|-qfTY)}32HkAz$_mibYV!I)u>bmjK`qwBe(>za^0Kt*HnFbSdO z1>+ryKCNxmm^)*$XfiDOF2|{-v3KKB?&!(S_Y=Ht@|ir^hLd978xuI&N{k>?(*f8H z=ClxVJK_%_z1TH0eUwm2J+2To7FK4o+n_na)&#VLn1m;!+CX+~WC+qg1?PA~KdOlC zW)C@pw75_xoe=w7i|r9KGIvQ$+3K?L{7TGHwrQM{dCp=Z*D}3kX7E-@sZnup!BImw z*T#a=+WcTwL78exTgBn|iNE3#EsOorO z*kt)gDzHiPt07fmisA2LWN?AymkdqTgr?=loT7z@d`wnlr6oN}@o|&JX!yPzC*Y8d zu6kWlTzE1)ckyBn+0Y^HMN+GA$wUO_LN6W>mxCo!0?oiQvT`z$jbSEu&{UHRU0E8# z%B^wOc@S!yhMT49Y)ww(Xta^8pmPCe@eI5C*ed96)AX9<>))nKx0(sci8gwob_1}4 z0DIL&vsJ1_s%<@y%U*-eX z5rN&(zef-5G~?@r79oZGW1d!WaTqQn0F6RIOa9tJ=0(kdd{d1{<*tHT#cCvl*i>YY zH+L7jq8xZNcTUBqj(S)ztTU!TM!RQ}In*n&Gn<>(60G7}4%WQL!o>hbJqNDSGwl#H z`4k+twp0cj%PsS+NKaxslAEu9!#U3xT1|_KB6`h=PI0SW`P9GTa7caD1}vKEglV8# zjKZR`pluCW19c2fM&ZG)c3T3Um;ir3y(tSCJ7Agl6|b524dy5El{^EQBG?E61H0XY z`bqg!;zhGhyMFl&(o=JWEJ8n~z)xI}A@C0d2hQGvw7nGv)?POU@(kS1m=%`|+^ika zXl8zjS?xqW$WlO?Ewa;vF~XbybHBor$f<%I&*t$F5fynwZlTGj|IjZtVfGa7l&tK} zW>I<69w(cZLu)QIVG|M2xzW@S+70NinQzk&Y0+3WT*cC)rx~04O-^<{JohU_&HL5XdUKW!uFy|i$FB|EMu0eUyW;gsf`XfIc!Z0V zeK&*hPL}f_cX=@iv>K%S5kL;cl_$v?n(Q9f_cChk8Lq$glT|=e+T*8O4H2n<=NGmn z+2*h+v;kBvF>}&0RDS>)B{1!_*XuE8A$Y=G8w^qGMtfudDBsD5>T5SB;Qo}fSkkiV ze^K^M(UthkwrD!&*tTsu>Dacdj_q`~V%r_twr$(Ct&_dKeeXE?fA&4&yASJWJ*}~- zel=@W)tusynfC_YqH4ll>4Eg`Xjs5F7Tj>tTLz<0N3)X<1px_d2yUY>X~y>>93*$) z5PuNMQLf9Bu?AAGO~a_|J2akO1M*@VYN^VxvP0F$2>;Zb9;d5Yfd8P%oFCCoZE$ z4#N$^J8rxYjUE_6{T%Y>MmWfHgScpuGv59#4u6fpTF%~KB^Ae`t1TD_^Ud#DhL+Dm zbY^VAM#MrAmFj{3-BpVSWph2b_Y6gCnCAombVa|1S@DU)2r9W<> zT5L8BB^er3zxKt1v(y&OYk!^aoQisqU zH(g@_o)D~BufUXcPt!Ydom)e|aW{XiMnes2z&rE?og>7|G+tp7&^;q?Qz5S5^yd$i z8lWr4g5nctBHtigX%0%XzIAB8U|T6&JsC4&^hZBw^*aIcuNO47de?|pGXJ4t}BB`L^d8tD`H`i zqrP8?#J@8T#;{^B!KO6J=@OWKhAerih(phML`(Rg7N1XWf1TN>=Z3Do{l_!d~DND&)O)D>ta20}@Lt77qSnVsA7>)uZAaT9bsB>u&aUQl+7GiY2|dAEg@%Al3i316y;&IhQL^8fw_nwS>f60M_-m+!5)S_6EPM7Y)(Nq^8gL7(3 zOiot`6Wy6%vw~a_H?1hLVzIT^i1;HedHgW9-P#)}Y6vF%C=P70X0Tk^z9Te@kPILI z_(gk!k+0%CG)%!WnBjjw*kAKs_lf#=5HXC00s-}oM-Q1aXYLj)(1d!_a7 z*Gg4Fe6F$*ujVjI|79Z5+Pr`us%zW@ln++2l+0hsngv<{mJ%?OfSo_3HJXOCys{Ug z00*YR-(fv<=&%Q!j%b-_ppA$JsTm^_L4x`$k{VpfLI(FMCap%LFAyq;#ns5bR7V+x zO!o;c5y~DyBPqdVQX)8G^G&jWkBy2|oWTw>)?5u}SAsI$RjT#)lTV&Rf8;>u*qXnb z8F%Xb=7#$m)83z%`E;49)t3fHInhtc#kx4wSLLms!*~Z$V?bTyUGiS&m>1P(952(H zuHdv=;o*{;5#X-uAyon`hP}d#U{uDlV?W?_5UjJvf%11hKwe&(&9_~{W)*y1nR5f_ z!N(R74nNK`y8>B!0Bt_Vr!;nc3W>~RiKtGSBkNlsR#-t^&;$W#)f9tTlZz>n*+Fjz z3zXZ;jf(sTM(oDzJt4FJS*8c&;PLTW(IQDFs_5QPy+7yhi1syPCarvqrHFcf&yTy)^O<1EBx;Ir`5W{TIM>{8w&PB>ro4;YD<5LF^TjTb0!zAP|QijA+1Vg>{Afv^% zmrkc4o6rvBI;Q8rj4*=AZacy*n8B{&G3VJc)so4$XUoie0)vr;qzPZVbb<#Fc=j+8CGBWe$n|3K& z_@%?{l|TzKSlUEO{U{{%Fz_pVDxs7i9H#bnbCw7@4DR=}r_qV!Zo~CvD4ZI*+j3kO zW6_=|S`)(*gM0Z;;}nj`73OigF4p6_NPZQ-Od~e$c_);;4-7sR>+2u$6m$Gf%T{aq zle>e3(*Rt(TPD}03n5)!Ca8Pu!V}m6v0o1;5<1h$*|7z|^(3$Y&;KHKTT}hV056wuF0Xo@mK-52~r=6^SI1NC%c~CC?n>yX6wPTgiWYVz!Sx^atLby9YNn1Rk{g?|pJaxD4|9cUf|V1_I*w zzxK)hRh9%zOl=*$?XUjly5z8?jPMy%vEN)f%T*|WO|bp5NWv@B(K3D6LMl!-6dQg0 zXNE&O>Oyf%K@`ngCvbGPR>HRg5!1IV$_}m@3dWB7x3t&KFyOJn9pxRXCAzFr&%37wXG;z^xaO$ekR=LJG ztIHpY8F5xBP{mtQidqNRoz= z@){+N3(VO5bD+VrmS^YjG@+JO{EOIW)9=F4v_$Ed8rZtHvjpiEp{r^c4F6Ic#ChlC zJX^DtSK+v(YdCW)^EFcs=XP7S>Y!4=xgmv>{S$~@h=xW-G4FF9?I@zYN$e5oF9g$# zb!eVU#J+NjLyX;yb)%SY)xJdvGhsnE*JEkuOVo^k5PyS=o#vq!KD46UTW_%R=Y&0G zFj6bV{`Y6)YoKgqnir2&+sl+i6foAn-**Zd1{_;Zb7Ki=u394C5J{l^H@XN`_6XTKY%X1AgQM6KycJ+= zYO=&t#5oSKB^pYhNdzPgH~aEGW2=ec1O#s-KG z71}LOg@4UEFtp3GY1PBemXpNs6UK-ax*)#$J^pC_me;Z$Je(OqLoh|ZrW*mAMBFn< zHttjwC&fkVfMnQeen8`Rvy^$pNRFVaiEN4Pih*Y3@jo!T0nsClN)pdrr9AYLcZxZ| zJ5Wlj+4q~($hbtuY zVQ7hl>4-+@6g1i`1a)rvtp-;b0>^`Dloy(#{z~ytgv=j4q^Kl}wD>K_Y!l~ zp(_&7sh`vfO(1*MO!B%<6E_bx1)&s+Ae`O)a|X=J9y~XDa@UB`m)`tSG4AUhoM=5& znWoHlA-(z@3n0=l{E)R-p8sB9XkV zZ#D8wietfHL?J5X0%&fGg@MH~(rNS2`GHS4xTo7L$>TPme+Is~!|79=^}QbPF>m%J zFMkGzSndiPO|E~hrhCeo@&Ea{M(ieIgRWMf)E}qeTxT8Q#g-!Lu*x$v8W^M^>?-g= zwMJ$dThI|~M06rG$Sv@C@tWR>_YgaG&!BAbkGggVQa#KdtDB)lMLNVLN|51C@F^y8 zCRvMB^{GO@j=cHfmy}_pCGbP%xb{pNN>? z?7tBz$1^zVaP|uaatYaIN+#xEN4jBzwZ|YI_)p(4CUAz1ZEbDk>J~Y|63SZaak~#0 zoYKruYsWHoOlC1(MhTnsdUOwQfz5p6-D0}4;DO$B;7#M{3lSE^jnTT;ns`>!G%i*F?@pR1JO{QTuD0U+~SlZxcc8~>IB{)@8p`P&+nDxNj`*gh|u?yrv$phpQcW)Us)bi`kT%qLj(fi{dWRZ%Es2!=3mI~UxiW0$-v3vUl?#g{p6eF zMEUAqo5-L0Ar(s{VlR9g=j7+lt!gP!UN2ICMokAZ5(Agd>})#gkA2w|5+<%-CuEP# zqgcM}u@3(QIC^Gx<2dbLj?cFSws_f3e%f4jeR?4M^M3cx1f+Qr6ydQ>n)kz1s##2w zk}UyQc+Z5G-d-1}{WzjkLXgS-2P7auWSJ%pSnD|Uivj5u!xk0 z_^-N9r9o;(rFDt~q1PvE#iJZ_f>J3gcP$)SOqhE~pD2|$=GvpL^d!r z6u=sp-CrMoF7;)}Zd7XO4XihC4ji?>V&(t^?@3Q&t9Mx=qex6C9d%{FE6dvU6%d94 zIE;hJ1J)cCqjv?F``7I*6bc#X)JW2b4f$L^>j{*$R`%5VHFi*+Q$2;nyieduE}qdS{L8y8F08yLs?w}{>8>$3236T-VMh@B zq-nujsb_1aUv_7g#)*rf9h%sFj*^mIcImRV*k~Vmw;%;YH(&ylYpy!&UjUVqqtfG` zox3esju?`unJJA_zKXRJP)rA3nXc$m^{S&-p|v|-0x9LHJm;XIww7C#R$?00l&Yyj z=e}gKUOpsImwW?N)+E(awoF@HyP^EhL+GlNB#k?R<2>95hz!h9sF@U20DHSB3~WMa zk90+858r@-+vWwkawJ)8ougd(i#1m3GLN{iSTylYz$brAsP%=&m$mQQrH$g%3-^VR zE%B`Vi&m8f3T~&myTEK28BDWCVzfWir1I?03;pX))|kY5ClO^+bae z*7E?g=3g7EiisYOrE+lA)2?Ln6q2*HLNpZEWMB|O-JI_oaHZB%CvYB(%=tU= zE*OY%QY58fW#RG5=gm0NR#iMB=EuNF@)%oZJ}nmm=tsJ?eGjia{e{yuU0l3{d^D@)kVDt=1PE)&tf_hHC%0MB znL|CRCPC}SeuVTdf>-QV70`0(EHizc21s^sU>y%hW0t!0&y<7}Wi-wGy>m%(-jsDj zP?mF|>p_K>liZ6ZP(w5(|9Ga%>tLgb$|doDDfkdW>Z z`)>V2XC?NJT26mL^@ zf+IKr27TfM!UbZ@?zRddC7#6ss1sw%CXJ4FWC+t3lHZupzM77m^=9 z&(a?-LxIq}*nvv)y?27lZ{j zifdl9hyJudyP2LpU$-kXctshbJDKS{WfulP5Dk~xU4Le4c#h^(YjJit4#R8_khheS z|8(>2ibaHES4+J|DBM7I#QF5u-*EdN{n=Kt@4Zt?@Tv{JZA{`4 zU#kYOv{#A&gGPwT+$Ud}AXlK3K7hYzo$(fBSFjrP{QQ zeaKg--L&jh$9N}`pu{Bs>?eDFPaWY4|9|foN%}i;3%;@4{dc+iw>m}{3rELqH21G! z`8@;w-zsJ1H(N3%|1B@#ioLOjib)j`EiJqPQVSbPSPVHCj6t5J&(NcWzBrzCiDt{4 zdlPAUKldz%6x5II1H_+jv)(xVL+a;P+-1hv_pM>gMRr%04@k;DTokASSKKhU1Qms| zrWh3a!b(J3n0>-tipg{a?UaKsP7?+|@A+1WPDiQIW1Sf@qDU~M_P65_s}7(gjTn0X zucyEm)o;f8UyshMy&>^SC3I|C6jR*R_GFwGranWZe*I>K+0k}pBuET&M~ z;Odo*ZcT?ZpduHyrf8E%IBFtv;JQ!N_m>!sV6ly$_1D{(&nO~w)G~Y`7sD3#hQk%^ zp}ucDF_$!6DAz*PM8yE(&~;%|=+h(Rn-=1Wykas_-@d&z#=S}rDf`4w(rVlcF&lF! z=1)M3YVz7orwk^BXhslJ8jR);sh^knJW(Qmm(QdSgIAIdlN4Te5KJisifjr?eB{FjAX1a0AB>d?qY4Wx>BZ8&}5K0fA+d{l8 z?^s&l8#j7pR&ijD?0b%;lL9l$P_mi2^*_OL+b}4kuLR$GAf85sOo02?Y#90}CCDiS zZ%rbCw>=H~CBO=C_JVV=xgDe%b4FaEFtuS7Q1##y686r%F6I)s-~2(}PWK|Z8M+Gu zl$y~5@#0Ka%$M<&Cv%L`a8X^@tY&T7<0|(6dNT=EsRe0%kp1Qyq!^43VAKYnr*A5~ zsI%lK1ewqO;0TpLrT9v}!@vJK{QoVa_+N4FYT#h?Y8rS1S&-G+m$FNMP?(8N`MZP zels(*?kK{{^g9DOzkuZXJ2;SrOQsp9T$hwRB1(phw1c7`!Q!by?Q#YsSM#I12RhU{$Q+{xj83axHcftEc$mNJ8_T7A-BQc*k(sZ+~NsO~xAA zxnbb%dam_fZlHvW7fKXrB~F&jS<4FD2FqY?VG?ix*r~MDXCE^WQ|W|WM;gsIA4lQP zJ2hAK@CF*3*VqPr2eeg6GzWFlICi8S>nO>5HvWzyZTE)hlkdC_>pBej*>o0EOHR|) z$?};&I4+_?wvL*g#PJ9)!bc#9BJu1(*RdNEn>#Oxta(VWeM40ola<0aOe2kSS~{^P zDJBd}0L-P#O-CzX*%+$#v;(x%<*SPgAje=F{Zh-@ucd2DA(yC|N_|ocs*|-!H%wEw z@Q!>siv2W;C^^j^59OAX03&}&D*W4EjCvfi(ygcL#~t8XGa#|NPO+*M@Y-)ctFA@I z-p7npT1#5zOLo>7q?aZpCZ=iecn3QYklP;gF0bq@>oyBq94f6C=;Csw3PkZ|5q=(c zfs`aw?II0e(h=|7o&T+hq&m$; zBrE09Twxd9BJ2P+QPN}*OdZ-JZV7%av@OM7v!!NL8R;%WFq*?{9T3{ct@2EKgc8h) zMxoM$SaF#p<`65BwIDfmXG6+OiK0e)`I=!A3E`+K@61f}0e z!2a*FOaDrOe>U`q%K!QN`&=&0C~)CaL3R4VY(NDt{Xz(Xpqru5=r#uQN1L$Je1*dkdqQ*=lofQaN%lO!<5z9ZlHgxt|`THd>2 zsWfU$9=p;yLyJyM^t zS2w9w?Bpto`@H^xJpZDKR1@~^30Il6oFGfk5%g6w*C+VM)+%R@gfIwNprOV5{F^M2 zO?n3DEzpT+EoSV-%OdvZvNF+pDd-ZVZ&d8 zKeIyrrfPN=EcFRCPEDCVflX#3-)Ik_HCkL(ejmY8vzcf-MTA{oHk!R2*36`O68$7J zf}zJC+bbQk--9Xm!u#lgLvx8TXx2J258E5^*IZ(FXMpq$2LUUvhWQPs((z1+2{Op% z?J}9k5^N=z;7ja~zi8a_-exIqWUBJwohe#4QJ`|FF*$C{lM18z^#hX6!5B8KAkLUX ziP=oti-gpV(BsLD{0(3*dw}4JxK23Y7M{BeFPucw!sHpY&l%Ws4pSm`+~V7;bZ%Dx zeI)MK=4vC&5#;2MT7fS?^ch9?2;%<8Jlu-IB&N~gg8t;6S-#C@!NU{`p7M8@2iGc& zg|JPg%@gCoCQ&s6JvDU&`X2S<57f(k8nJ1wvBu{8r?;q3_kpZZ${?|( z+^)UvR33sjSd)aT!UPkA;ylO6{aE3MQa{g%Mcf$1KONcjO@&g5zPHWtzM1rYC{_K> zgQNcs<{&X{OA=cEWw5JGqpr0O>x*Tfak2PE9?FuWtz^DDNI}rwAaT0(bdo-<+SJ6A z&}S%boGMWIS0L}=S>|-#kRX;e^sUsotry(MjE|3_9duvfc|nwF#NHuM-w7ZU!5ei8 z6Mkf>2)WunY2eU@C-Uj-A zG(z0Tz2YoBk>zCz_9-)4a>T46$(~kF+Y{#sA9MWH%5z#zNoz)sdXq7ZR_+`RZ%0(q zC7&GyS_|BGHNFl8Xa%@>iWh%Gr?=J5<(!OEjauj5jyrA-QXBjn0OAhJJ9+v=!LK`` z@g(`^*84Q4jcDL`OA&ZV60djgwG`|bcD*i50O}Q{9_noRg|~?dj%VtKOnyRs$Uzqg z191aWoR^rDX#@iSq0n z?9Sg$WSRPqSeI<}&n1T3!6%Wj@5iw5`*`Btni~G=&;J+4`7g#OQTa>u`{4ZZ(c@s$ zK0y;ySOGD-UTjREKbru{QaS>HjN<2)R%Nn-TZiQ(Twe4p@-saNa3~p{?^V9Nixz@a zykPv~<@lu6-Ng9i$Lrk(xi2Tri3q=RW`BJYOPC;S0Yly%77c727Yj-d1vF!Fuk{Xh z)lMbA69y7*5ufET>P*gXQrxsW+ zz)*MbHZv*eJPEXYE<6g6_M7N%#%mR{#awV3i^PafNv(zyI)&bH?F}2s8_rR(6%!V4SOWlup`TKAb@ee>!9JKPM=&8g#BeYRH9FpFybxBXQI2|g}FGJfJ+ zY-*2hB?o{TVL;Wt_ek;AP5PBqfDR4@Z->_182W z{P@Mc27j6jE*9xG{R$>6_;i=y{qf(c`5w9fa*`rEzX6t!KJ(p1H|>J1pC-2zqWENF zmm=Z5B4u{cY2XYl(PfrInB*~WGWik3@1oRhiMOS|D;acnf-Bs(QCm#wR;@Vf!hOPJ zgjhDCfDj$HcyVLJ=AaTbQ{@vIv14LWWF$=i-BDoC11}V;2V8A`S>_x)vIq44-VB-v z*w-d}$G+Ql?En8j!~ZkCpQ$|cA0|+rrY>tiCeWxkRGPoarxlGU2?7%k#F693RHT24 z-?JsiXlT2PTqZqNb&sSc>$d;O4V@|b6VKSWQb~bUaWn1Cf0+K%`Q&Wc<>mQ>*iEGB zbZ;aYOotBZ{vH3y<0A*L0QVM|#rf*LIsGx(O*-7)r@yyBIzJnBFSKBUSl1e|8lxU* zzFL+YDVVkIuzFWeJ8AbgN&w(4-7zbiaMn{5!JQXu)SELk*CNL+Fro|2v|YO)1l15t zs(0^&EB6DPMyaqvY>=KL>)tEpsn;N5Q#yJj<9}ImL((SqErWN3Q=;tBO~ExTCs9hB z2E$7eN#5wX4<3m^5pdjm#5o>s#eS_Q^P)tm$@SawTqF*1dj_i#)3};JslbLKHXl_N z)Fxzf>FN)EK&Rz&*|6&%Hs-^f{V|+_vL1S;-1K-l$5xiC@}%uDuwHYhmsV?YcOUlk zOYkG5v2+`+UWqpn0aaaqrD3lYdh0*!L`3FAsNKu=Q!vJu?Yc8n|CoYyDo_`r0mPoo z8>XCo$W4>l(==h?2~PoRR*kEe)&IH{1sM41mO#-36`02m#nTX{r*r`Q5rZ2-sE|nA zhnn5T#s#v`52T5|?GNS`%HgS2;R(*|^egNPDzzH_z^W)-Q98~$#YAe)cEZ%vge965AS_am#DK#pjPRr-!^za8>`kksCAUj(Xr*1NW5~e zpypt_eJpD&4_bl_y?G%>^L}=>xAaV>KR6;^aBytqpiHe%!j;&MzI_>Sx7O%F%D*8s zSN}cS^<{iiK)=Ji`FpO#^zY!_|D)qeRNAtgmH)m;qC|mq^j(|hL`7uBz+ULUj37gj zksdbnU+LSVo35riSX_4z{UX=%n&}7s0{WuZYoSfwAP`8aKN9P@%e=~1`~1ASL-z%# zw>DO&ixr}c9%4InGc*_y42bdEk)ZdG7-mTu0bD@_vGAr*NcFoMW;@r?@LUhRI zCUJgHb`O?M3!w)|CPu~ej%fddw20lod?Ufp8Dmt0PbnA0J%KE^2~AIcnKP()025V> zG>noSM3$5Btmc$GZoyP^v1@Poz0FD(6YSTH@aD0}BXva?LphAiSz9f&Y(aDAzBnUh z?d2m``~{z;{}kZJ>a^wYI?ry(V9hIoh;|EFc0*-#*`$T0DRQ1;WsqInG;YPS+I4{g zJGpKk%%Sdc5xBa$Q^_I~(F97eqDO7AN3EN0u)PNBAb+n+ zWBTxQx^;O9o0`=g+Zrt_{lP!sgWZHW?8bLYS$;1a@&7w9rD9|Ge;Gb?sEjFoF9-6v z#!2)t{DMHZ2@0W*fCx;62d#;jouz`R5Y(t{BT=$N4yr^^o$ON8d{PQ=!O zX17^CrdM~7D-;ZrC!||<+FEOxI_WI3CA<35va%4v>gc zEX-@h8esj=a4szW7x{0g$hwoWRQG$yK{@3mqd-jYiVofJE!Wok1* znV7Gm&Ssq#hFuvj1sRyHg(6PFA5U*Q8Rx>-blOs=lb`qa{zFy&n4xY;sd$fE+<3EI z##W$P9M{B3c3Si9gw^jlPU-JqD~Cye;wr=XkV7BSv#6}DrsXWFJ3eUNrc%7{=^sP> zrp)BWKA9<}^R9g!0q7yWlh;gr_TEOD|#BmGq<@IV;ueg+D2}cjpp+dPf&Q(36sFU&K8}hA85U61faW&{ zlB`9HUl-WWCG|<1XANN3JVAkRYvr5U4q6;!G*MTdSUt*Mi=z_y3B1A9j-@aK{lNvx zK%p23>M&=KTCgR!Ee8c?DAO2_R?B zkaqr6^BSP!8dHXxj%N1l+V$_%vzHjqvu7p@%Nl6;>y*S}M!B=pz=aqUV#`;h%M0rU zHfcog>kv3UZAEB*g7Er@t6CF8kHDmKTjO@rejA^ULqn!`LwrEwOVmHx^;g|5PHm#B zZ+jjWgjJ!043F+&#_;D*mz%Q60=L9Ove|$gU&~As5^uz@2-BfQ!bW)Khn}G+Wyjw- z19qI#oB(RSNydn0t~;tAmK!P-d{b-@@E5|cdgOS#!>%#Rj6ynkMvaW@37E>@hJP^8 z2zk8VXx|>#R^JCcWdBCy{0nPmYFOxN55#^-rlqobe0#L6)bi?E?SPymF*a5oDDeSd zO0gx?#KMoOd&G(2O@*W)HgX6y_aa6iMCl^~`{@UR`nMQE`>n_{_aY5nA}vqU8mt8H z`oa=g0SyiLd~BxAj2~l$zRSDHxvDs;I4>+M$W`HbJ|g&P+$!U7-PHX4RAcR0szJ*( ze-417=bO2q{492SWrqDK+L3#ChUHtz*@MP)e^%@>_&#Yk^1|tv@j4%3T)diEX zATx4K*hcO`sY$jk#jN5WD<=C3nvuVsRh||qDHnc~;Kf59zr0;c7VkVSUPD%NnnJC_ zl3F^#f_rDu8l}l8qcAz0FFa)EAt32IUy_JLIhU_J^l~FRH&6-ivSpG2PRqzDdMWft>Zc(c)#tb%wgmWN%>IOPm zZi-noqS!^Ftb81pRcQi`X#UhWK70hy4tGW1mz|+vI8c*h@ zfFGJtW3r>qV>1Z0r|L>7I3un^gcep$AAWfZHRvB|E*kktY$qQP_$YG60C@X~tTQjB3%@`uz!qxtxF+LE!+=nrS^07hn` zEgAp!h|r03h7B!$#OZW#ACD+M;-5J!W+{h|6I;5cNnE(Y863%1(oH}_FTW})8zYb$7czP zg~Szk1+_NTm6SJ0MS_|oSz%e(S~P-&SFp;!k?uFayytV$8HPwuyELSXOs^27XvK-D zOx-Dl!P|28DK6iX>p#Yb%3`A&CG0X2S43FjN%IB}q(!hC$fG}yl1y9W&W&I@KTg6@ zK^kpH8=yFuP+vI^+59|3%Zqnb5lTDAykf z9S#X`3N(X^SpdMyWQGOQRjhiwlj!0W-yD<3aEj^&X%=?`6lCy~?`&WSWt z?U~EKFcCG_RJ(Qp7j=$I%H8t)Z@6VjA#>1f@EYiS8MRHZphp zMA_5`znM=pzUpBPO)pXGYpQ6gkine{6u_o!P@Q+NKJ}k!_X7u|qfpAyIJb$_#3@wJ z<1SE2Edkfk9C!0t%}8Yio09^F`YGzpaJHGk*-ffsn85@)%4@`;Fv^8q(-Wk7r=Q8p zT&hD`5(f?M{gfzGbbwh8(}G#|#fDuk7v1W)5H9wkorE0ZZjL0Q1=NRGY>zwgfm81DdoaVwNH;or{{eSyybt)m<=zXoA^RALYG-2t zouH|L*BLvmm9cdMmn+KGopyR@4*=&0&4g|FLoreZOhRmh=)R0bg~ zT2(8V_q7~42-zvb)+y959OAv!V$u(O3)%Es0M@CRFmG{5sovIq4%8Ahjk#*5w{+)+ zMWQoJI_r$HxL5km1#6(e@{lK3Udc~n0@g`g$s?VrnQJ$!oPnb?IHh-1qA`Rz$)Ai< z6w$-MJW-gKNvOhL+XMbE7&mFt`x1KY>k4(!KbbpZ`>`K@1J<(#vVbjx@Z@(6Q}MF# zMnbr-f55(cTa^q4+#)=s+ThMaV~E`B8V=|W_fZWDwiso8tNMTNse)RNBGi=gVwgg% zbOg8>mbRN%7^Um-7oj4=6`$|(K7!+t^90a{$18Z>}<#!bm%ZEFQ{X(yBZMc>lCz0f1I2w9Sq zuGh<9<=AO&g6BZte6hn>Qmvv;Rt)*cJfTr2=~EnGD8P$v3R|&1RCl&7)b+`=QGapi zPbLg_pxm`+HZurtFZ;wZ=`Vk*do~$wB zxoW&=j0OTbQ=Q%S8XJ%~qoa3Ea|au5o}_(P;=!y-AjFrERh%8la!z6Fn@lR?^E~H12D?8#ht=1F;7@o4$Q8GDj;sSC%Jfn01xgL&%F2 zwG1|5ikb^qHv&9hT8w83+yv&BQXOQyMVJSBL(Ky~p)gU3#%|blG?IR9rP^zUbs7rOA0X52Ao=GRt@C&zlyjNLv-} z9?*x{y(`509qhCV*B47f2hLrGl^<@SuRGR!KwHei?!CM10Tq*YDIoBNyRuO*>3FU? zHjipIE#B~y3FSfOsMfj~F9PNr*H?0oHyYB^G(YyNh{SxcE(Y-`x5jFMKb~HO*m+R% zrq|ic4fzJ#USpTm;X7K+E%xsT_3VHKe?*uc4-FsILUH;kL>_okY(w`VU*8+l>o>Jm ziU#?2^`>arnsl#)*R&nf_%>A+qwl%o{l(u)M?DK1^mf260_oteV3#E_>6Y4!_hhVD zM8AI6MM2V*^_M^sQ0dmHu11fy^kOqXqzpr?K$`}BKWG`=Es(9&S@K@)ZjA{lj3ea7_MBP zk(|hBFRjHVMN!sNUkrB;(cTP)T97M$0Dtc&UXSec<+q?y>5=)}S~{Z@ua;1xt@=T5 zI7{`Z=z_X*no8s>mY;>BvEXK%b`a6(DTS6t&b!vf_z#HM{Uoy_5fiB(zpkF{})ruka$iX*~pq1ZxD?q68dIo zIZSVls9kFGsTwvr4{T_LidcWtt$u{kJlW7moRaH6+A5hW&;;2O#$oKyEN8kx`LmG)Wfq4ykh+q{I3|RfVpkR&QH_x;t41Uw z`P+tft^E2B$domKT@|nNW`EHwyj>&}K;eDpe z1bNOh=fvIfk`&B61+S8ND<(KC%>y&?>opCnY*r5M+!UrWKxv0_QvTlJc>X#AaI^xo zaRXL}t5Ej_Z$y*|w*$6D+A?Lw-CO-$itm^{2Ct82-<0IW)0KMNvJHgBrdsIR0v~=H z?n6^}l{D``Me90`^o|q!olsF?UX3YSq^6Vu>Ijm>>PaZI8G@<^NGw{Cx&%|PwYrfw zR!gX_%AR=L3BFsf8LxI|K^J}deh0ZdV?$3r--FEX`#INxsOG6_=!v)DI>0q|BxT)z z-G6kzA01M?rba+G_mwNMQD1mbVbNTWmBi*{s_v_Ft9m2Avg!^78(QFu&n6mbRJ2bA zv!b;%yo{g*9l2)>tsZJOOp}U~8VUH`}$ z8p_}t*XIOehezolNa-a2x0BS})Y9}&*TPgua{Ewn-=wVrmJUeU39EKx+%w%=ixQWK zDLpwaNJs65#6o7Ln7~~X+p_o2BR1g~VCfxLzxA{HlWAI6^H;`juI=&r1jQrUv_q0Z z1Ja-tjdktrrP>GOC*#p?*xfQU5MqjMsBe!9lh(u8)w$e@Z|>aUHI5o;MGw*|Myiz3 z-f0;pHg~Q#%*Kx8MxH%AluVXjG2C$)WL-K63@Q`#y9_k_+}eR(x4~dp7oV-ek0H>I zgy8p#i4GN{>#v=pFYUQT(g&b$OeTy-X_#FDgNF8XyfGY6R!>inYn8IR2RDa&O!(6< znXs{W!bkP|s_YI*Yx%4stI`=ZO45IK6rBs`g7sP40ic}GZ58s?Mc$&i`kq_tfci>N zIHrC0H+Qpam1bNa=(`SRKjixBTtm&e`j9porEci!zdlg1RI0Jw#b(_Tb@RQK1Zxr_ z%7SUeH6=TrXt3J@js`4iDD0=IoHhK~I7^W8^Rcp~Yaf>2wVe|Hh1bUpX9ATD#moByY57-f2Ef1TP^lBi&p5_s7WGG9|0T}dlfxOx zXvScJO1Cnq`c`~{Dp;{;l<-KkCDE+pmexJkd}zCgE{eF=)K``-qC~IT6GcRog_)!X z?fK^F8UDz$(zFUrwuR$qro5>qqn>+Z%<5>;_*3pZ8QM|yv9CAtrAx;($>4l^_$_-L z*&?(77!-=zvnCVW&kUcZMb6;2!83si518Y%R*A3JZ8Is|kUCMu`!vxDgaWjs7^0j( ziTaS4HhQ)ldR=r)_7vYFUr%THE}cPF{0H45FJ5MQW^+W>P+eEX2kLp3zzFe*-pFVA zdDZRybv?H|>`9f$AKVjFWJ=wegO7hOOIYCtd?Vj{EYLT*^gl35|HQ`R=ti+ADm{jyQE7K@kdjuqJhWVSks>b^ zxha88-h3s;%3_5b1TqFCPTxVjvuB5U>v=HyZ$?JSk+&I%)M7KE*wOg<)1-Iy)8-K! z^XpIt|0ibmk9RtMmlUd7#Ap3Q!q9N4atQy)TmrhrFhfx1DAN`^vq@Q_SRl|V z#lU<~n67$mT)NvHh`%als+G-)x1`Y%4Bp*6Un5Ri9h=_Db zA-AdP!f>f0m@~>7X#uBM?diI@)Egjuz@jXKvm zJo+==juc9_<;CqeRaU9_Mz@;3e=E4=6TK+c`|uu#pIqhSyNm`G(X)&)B`8q0RBv#> z`gGlw(Q=1Xmf55VHj%C#^1lpc>LY8kfA@|rlC1EA<1#`iuyNO z(=;irt{_&K=i4)^x%;U(Xv<)+o=dczC5H3W~+e|f~{*ucxj@{Yi-cw^MqYr3fN zF5D+~!wd$#al?UfMnz(@K#wn`_5na@rRr8XqN@&M&FGEC@`+OEv}sI1hw>Up0qAWf zL#e4~&oM;TVfjRE+10B_gFlLEP9?Q-dARr3xi6nQqnw>k-S;~b z;!0s2VS4}W8b&pGuK=7im+t(`nz@FnT#VD|!)eQNp-W6)@>aA+j~K*H{$G`y2|QHY z|Hmy+CR@#jWY4~)lr1qBJB_RfHJFfP<}pK5(#ZZGSqcpyS&}01LnTWk5fzmXMGHkJ zTP6L^B+uj;lmB_W<~4=${+v0>z31M!-_O@o-O9GyW)j_mjx}!0@br_LE-7SIuPP84 z;5=O(U*g_um0tyG|61N@d9lEuOeiRd+#NY^{nd5;-CVlw&Ap7J?qwM^?E29wvS}2d zbzar4Fz&RSR(-|s!Z6+za&Z zY#D<5q_JUktIzvL0)yq_kLWG6DO{ri=?c!y!f(Dk%G{8)k`Gym%j#!OgXVDD3;$&v@qy#ISJfp=Vm>pls@9-mapVQChAHHd-x+OGx)(*Yr zC1qDUTZ6mM(b_hi!TuFF2k#8uI2;kD70AQ&di$L*4P*Y-@p`jdm%_c3f)XhYD^6M8&#Y$ZpzQMcR|6nsH>b=*R_Von!$BTRj7yGCXokoAQ z&ANvx0-Epw`QIEPgI(^cS2f(Y85yV@ygI{ewyv5Frng)e}KCZF7JbR(&W618_dcEh(#+^zZFY;o<815<5sOHQdeax9_!PyM&;{P zkBa5xymca0#)c#tke@3KNEM8a_mT&1gm;p&&JlMGH(cL(b)BckgMQ^9&vRwj!~3@l zY?L5}=Jzr080OGKb|y`ee(+`flQg|!lo6>=H)X4`$Gz~hLmu2a%kYW_Uu8x09Pa0J zKZ`E$BKJ=2GPj_3l*TEcZ*uYRr<*J^#5pILTT;k_cgto1ZL-%slyc16J~OH-(RgDA z%;EjEnoUkZ&acS{Q8`{i6T5^nywgqQI5bDIymoa7CSZG|WWVk>GM9)zy*bNih|QIm z%0+(Nnc*a_xo;$=!HQYaapLms>J1ToyjtFByY`C2H1wT#178#4+|{H0BBqtCdd$L% z_3Hc60j@{t9~MjM@LBalR&6@>B;9?r<7J~F+WXyYu*y3?px*=8MAK@EA+jRX8{CG?GI-< z54?Dc9CAh>QTAvyOEm0^+x;r2BWX|{3$Y7)L5l*qVE*y0`7J>l2wCmW zL1?|a`pJ-l{fb_N;R(Z9UMiSj6pQjOvQ^%DvhIJF!+Th7jO2~1f1N+(-TyCFYQZYw z4)>7caf^Ki_KJ^Zx2JUb z&$3zJy!*+rCV4%jqwyuNY3j1ZEiltS0xTzd+=itTb;IPYpaf?8Y+RSdVdpacB(bVQ zC(JupLfFp8y43%PMj2}T|VS@%LVp>hv4Y!RPMF?pp8U_$xCJ)S zQx!69>bphNTIb9yn*_yfj{N%bY)t{L1cs8<8|!f$;UQ*}IN=2<6lA;x^(`8t?;+ST zh)z4qeYYgZkIy{$4x28O-pugO&gauRh3;lti9)9Pvw+^)0!h~%m&8Q!AKX%urEMnl z?yEz?g#ODn$UM`+Q#$Q!6|zsq_`dLO5YK-6bJM6ya>}H+vnW^h?o$z;V&wvuM$dR& zeEq;uUUh$XR`TWeC$$c&Jjau2it3#%J-y}Qm>nW*s?En?R&6w@sDXMEr#8~$=b(gk zwDC3)NtAP;M2BW_lL^5ShpK$D%@|BnD{=!Tq)o(5@z3i7Z){} zGr}Exom_qDO{kAVkZ*MbLNHE666Kina#D{&>Jy%~w7yX$oj;cYCd^p9zy z8*+wgSEcj$4{WxKmCF(5o7U4jqwEvO&dm1H#7z}%VXAbW&W24v-tS6N3}qrm1OnE)fUkoE8yMMn9S$?IswS88tQWm4#Oid#ckgr6 zRtHm!mfNl-`d>O*1~d7%;~n+{Rph6BBy^95zqI{K((E!iFQ+h*C3EsbxNo_aRm5gj zKYug($r*Q#W9`p%Bf{bi6;IY0v`pB^^qu)gbg9QHQ7 zWBj(a1YSu)~2RK8Pi#C>{DMlrqFb9e_RehEHyI{n?e3vL_}L>kYJC z_ly$$)zFi*SFyNrnOt(B*7E$??s67EO%DgoZL2XNk8iVx~X_)o++4oaK1M|ou73vA0K^503j@uuVmLcHH4ya-kOIDfM%5%(E z+Xpt~#7y2!KB&)PoyCA+$~DXqxPxxALy!g-O?<9+9KTk4Pgq4AIdUkl`1<1#j^cJg zgU3`0hkHj_jxV>`Y~%LAZl^3o0}`Sm@iw7kwff{M%VwtN)|~!p{AsfA6vB5UolF~d zHWS%*uBDt<9y!9v2Xe|au&1j&iR1HXCdyCjxSgG*L{wmTD4(NQ=mFjpa~xooc6kju z`~+d{j7$h-;HAB04H!Zscu^hZffL#9!p$)9>sRI|Yovm)g@F>ZnosF2EgkU3ln0bR zTA}|+E(tt)!SG)-bEJi_0m{l+(cAz^pi}`9=~n?y&;2eG;d9{M6nj>BHGn(KA2n|O zt}$=FPq!j`p&kQ8>cirSzkU0c08%8{^Qyqi-w2LoO8)^E7;;I1;HQ6B$u0nNaX2CY zSmfi)F`m94zL8>#zu;8|{aBui@RzRKBlP1&mfFxEC@%cjl?NBs`cr^nm){>;$g?rhKr$AO&6qV_Wbn^}5tfFBry^e1`%du2~o zs$~dN;S_#%iwwA_QvmMjh%Qo?0?rR~6liyN5Xmej8(*V9ym*T`xAhHih-v$7U}8=dfXi2i*aAB!xM(Xekg*ix@r|ymDw*{*s0?dlVys2e)z62u1 z+k3esbJE=-P5S$&KdFp+2H7_2e=}OKDrf( z9-207?6$@f4m4B+9E*e((Y89!q?zH|mz_vM>kp*HGXldO0Hg#!EtFhRuOm$u8e~a9 z5(roy7m$Kh+zjW6@zw{&20u?1f2uP&boD}$#Zy)4o&T;vyBoqFiF2t;*g=|1=)PxB z8eM3Mp=l_obbc?I^xyLz?4Y1YDWPa+nm;O<$Cn;@ane616`J9OO2r=rZr{I_Kizyc zP#^^WCdIEp*()rRT+*YZK>V@^Zs=ht32x>Kwe zab)@ZEffz;VM4{XA6e421^h~`ji5r%)B{wZu#hD}f3$y@L0JV9f3g{-RK!A?vBUA}${YF(vO4)@`6f1 z-A|}e#LN{)(eXloDnX4Vs7eH|<@{r#LodP@Nz--$Dg_Par%DCpu2>2jUnqy~|J?eZ zBG4FVsz_A+ibdwv>mLp>P!(t}E>$JGaK$R~;fb{O3($y1ssQQo|5M;^JqC?7qe|hg zu0ZOqeFcp?qVn&Qu7FQJ4hcFi&|nR!*j)MF#b}QO^lN%5)4p*D^H+B){n8%VPUzi! zDihoGcP71a6!ab`l^hK&*dYrVYzJ0)#}xVrp!e;lI!+x+bfCN0KXwUAPU9@#l7@0& QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.properties.mustache b/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.properties.mustache index 7c08e4f06..b82aa23a4 100644 --- a/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.properties.mustache +++ b/boat-scaffold/src/main/templates/boat-java/gradle-wrapper.properties.mustache @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/boat-scaffold/src/main/templates/boat-java/gradle.properties.mustache b/boat-scaffold/src/main/templates/boat-java/gradle.properties.mustache index bf46cf4d6..095ab31d1 100644 --- a/boat-scaffold/src/main/templates/boat-java/gradle.properties.mustache +++ b/boat-scaffold/src/main/templates/boat-java/gradle.properties.mustache @@ -6,4 +6,4 @@ #target = android {{#gradleProperties}} {{{.}}} -{{/gradleProperties}} \ No newline at end of file +{{/gradleProperties}} diff --git a/boat-scaffold/src/main/templates/boat-java/gradlew.bat.mustache b/boat-scaffold/src/main/templates/boat-java/gradlew.bat.mustache index 806627401..25da30dbd 100644 --- a/boat-scaffold/src/main/templates/boat-java/gradlew.bat.mustache +++ b/boat-scaffold/src/main/templates/boat-java/gradlew.bat.mustache @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -33,20 +34,20 @@ set APP_HOME=%DIRNAME% 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" +set DEFAULT_JVM_OPTS="-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 +if %ERRORLEVEL% equ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,15 +76,17 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 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 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal -:omega \ No newline at end of file +:omega diff --git a/boat-scaffold/src/main/templates/boat-java/gradlew.mustache b/boat-scaffold/src/main/templates/boat-java/gradlew.mustache old mode 100755 new mode 100644 index 1a33e4365..9d0ce634c --- a/boat-scaffold/src/main/templates/boat-java/gradlew.mustache +++ b/boat-scaffold/src/main/templates/boat-java/gradlew.mustache @@ -55,7 +55,7 @@ # 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 +# https://github.com/gradle/gradle/blob/HEAD/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/. @@ -69,37 +69,35 @@ 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" ] +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 +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" +# This is normally unused +# shellcheck disable=SC2034 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"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { - echo "$*" +echo "$*" } >&2 die () { - echo - echo "$*" - echo - exit 1 +echo +echo "$*" +echo +exit 1 } >&2 # OS specific support (must be 'true' or 'false'). @@ -108,10 +106,10 @@ msys=false darwin=false nonstop=false case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +CYGWIN* ) cygwin=true ;; #( +Darwin* ) darwin=true ;; #( +MSYS* | MINGW* ) msys=true ;; #( +NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -119,39 +117,46 @@ 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 +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 +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. +JAVACMD=java +if ! command -v java >/dev/null 2>&1 +then +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 +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 +case $MAX_FD in #( +max*) +# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. +# shellcheck disable=SC2039,SC3045 +MAX_FD=$( ulimit -H -n ) || +warn "Could not query maximum file descriptor limit" +esac +case $MAX_FD in #( +'' | soft) :;; #( +*) +# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. +# shellcheck disable=SC2039,SC3045 +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: @@ -164,46 +169,56 @@ fi # 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 +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. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" +"-Dorg.gradle.appname=$APP_BASE_NAME" \ +-classpath "$CLASSPATH" \ +org.gradle.wrapper.GradleWrapperMain \ +"$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then +die "xargs is not available" +fi # Use "xargs" to parse quoted args. # @@ -225,10 +240,10 @@ set -- \ # eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | +xargs -n1 | +sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | +tr '\n' ' ' +)" '"$@"' -exec "$JAVACMD" "$@" \ No newline at end of file +exec "$JAVACMD" "$@" diff --git a/boat-scaffold/src/main/templates/boat-java/jackson_annotations.mustache b/boat-scaffold/src/main/templates/boat-java/jackson_annotations.mustache index ccde126f5..08b43352e 100644 --- a/boat-scaffold/src/main/templates/boat-java/jackson_annotations.mustache +++ b/boat-scaffold/src/main/templates/boat-java/jackson_annotations.mustache @@ -4,16 +4,11 @@ * 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}}) + @JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}, required = {{#isNullable}}false{{/isNullable}}{{^isNullable}}{{required}}{{/isNullable}}) @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}} - {{#isXmlWrapped}} - // items.xmlName={{items.xmlName}} - @JacksonXmlElementWrapper(useWrapping = {{isXmlWrapped}}, {{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{#items.xmlName}}{{items.xmlName}}{{/items.xmlName}}{{^items.xmlName}}{{items.baseName}}{{/items.xmlName}}") - {{/isXmlWrapped}} - {{/isContainer}} - {{/withXml}} \ No newline at end of file +{{#withXml}} + @JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isContainer}} + @JacksonXmlElementWrapper({{#isXmlWrapped}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}", {{#xmlNamespace}}namespace = "{{.}}", {{/xmlNamespace}}{{/isXmlWrapped}}useWrapping = {{isXmlWrapped}}) + {{/isContainer}} +{{/withXml}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/javaBuilder.mustache b/boat-scaffold/src/main/templates/boat-java/javaBuilder.mustache new file mode 100644 index 000000000..4a0e102b8 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/javaBuilder.mustache @@ -0,0 +1,82 @@ +public static class Builder {{#parentModel}}extends {{classname}}.Builder {{/parentModel}}{ + + private {{classname}} instance; + + public Builder() { + this(new {{classname}}()); + } + + protected Builder({{classname}} instance) { + {{#parentModel}} + super(instance); + {{/parentModel}} + this.instance = instance; + } + + {{#vars}} + public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.instance.{{name}} = JsonNullable.<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}>of({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.instance.{{name}} = {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + return this; + } + {{#vendorExtensions.x-is-jackson-optional-nullable}} + public {{classname}}.Builder {{name}}(JsonNullable<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}> {{name}}) { + this.instance.{{name}} = {{name}}; + return this; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{/vars}} + +{{#parentVars}} + public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) { // inherited: {{isInherited}} + super.{{name}}({{name}}); + return this; + } + {{#vendorExtensions.x-is-jackson-optional-nullable}} + public {{classname}}.Builder {{name}}(JsonNullable<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}> {{name}}) { + this.instance.{{name}} = {{name}}; + return this; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}} + + {{/parentVars}} + + /** + * returns a built {{classname}} instance. + * + * The builder is not reusable. + */ + public {{classname}} build() { + try { + return this.instance; + } finally { + // ensure that this.instance is not reused{{#parentModel}} + super.build();{{/parentModel}} + this.instance = null; + } + } + + @Override + public String toString() { + return getClass() + "=(" + instance + ")"; + } + } + + /** + * Create a builder with no initialized field. + */ + public static {{classname}}.Builder builder() { + return new {{classname}}.Builder(); + } + + /** + * Create a builder with a shallow copy of this instance. + */ + public {{classname}}.Builder toBuilder() { + return new {{classname}}.Builder(){{#allVars}} + .{{name}}({{getter}}()){{/allVars}}; + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/ApiClient.mustache index 1a07e1cbd..f70c29c21 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/ApiClient.mustache @@ -8,34 +8,35 @@ 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.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.ParseException; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.FileEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.cookie.BasicClientCookie; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.apache.http.cookie.Cookie; +{{#openApiNullable}} +import org.openapitools.jackson.nullable.JsonNullableModule; +{{/openApiNullable}} + +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.FileEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; +import org.apache.hc.core5.http.message.BasicNameValuePair; import java.util.Collection; import java.util.Collections; @@ -47,6 +48,7 @@ import java.util.List; import java.util.Arrays; import java.util.ArrayList; import java.util.Date; +import java.util.function.Supplier; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -58,6 +60,8 @@ import java.io.InputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.nio.file.Paths; @@ -81,10 +85,11 @@ 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 Map defaultHeaderMap = new HashMap(); + protected Map defaultCookieMap = new HashMap(); + protected String basePath = "{{{basePath}}}"; protected List servers = new ArrayList({{#servers}}{{#-first}}Arrays.asList( {{/-first}} new ServerConfiguration( "{{{url}}}", @@ -112,27 +117,27 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { ){{/-last}}{{/servers}}); protected Integer serverIndex = 0; protected Map serverVariables = null; - private boolean debugging = false; - private int connectionTimeout = 0; + protected boolean debugging = false; + protected int connectionTimeout = 0; - private CloseableHttpClient httpClient; - private ObjectMapper objectMapper; + protected CloseableHttpClient httpClient; + protected ObjectMapper objectMapper; protected String tempFolderPath = null; - private Map authentications; + protected Map authentications; - private int statusCode; - private Map> responseHeaders; + protected ThreadLocal lastStatusCode = new ThreadLocal<>(); + protected ThreadLocal>> lastResponseHeaders = new ThreadLocal<>(); - private DateFormat dateFormat; + protected DateFormat dateFormat; // Methods that can have a request body - private static List bodyMethods = Arrays.asList("POST", "PUT", "DELETE", "PATCH"); + protected static List bodyMethods = Arrays.asList("POST", "PUT", "DELETE", "PATCH"); public ApiClient(CloseableHttpClient httpClient) { objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); @@ -141,6 +146,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { objectMapper.registerModule(new JodaModule()); {{/joda}} objectMapper.registerModule(new JavaTimeModule()); + {{#openApiNullable}} + objectMapper.registerModule(new JsonNullableModule()); + {{/openApiNullable}} + objectMapper.registerModule(new RFC3339JavaTimeModule()); objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat()); dateFormat = ApiClient.buildDefaultDateFormat(); @@ -150,8 +159,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + 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. @@ -272,16 +281,18 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @return Status code */ + @Deprecated public int getStatusCode() { - return statusCode; + return lastStatusCode.get(); } /** * Gets the response headers of the previous request * @return Response headers */ + @Deprecated public Map> getResponseHeaders() { - return responseHeaders; + return lastResponseHeaders.get(); } /** @@ -329,6 +340,20 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { throw new RuntimeException("No Bearer authentication configured!"); } + /** + * Helper method to set the supplier of access tokens for Bearer authentication. + * + * @param tokenSupplier the token supplier function + */ + public void setBearerToken(Supplier tokenSupplier) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBearerAuth) { + ((HttpBearerAuth) auth).setBearerToken(tokenSupplier); + return; + } + } + throw new RuntimeException("No Bearer authentication configured!"); + } {{/hasHttpBearerMethods}} {{#hasHttpBasicMethods}} @@ -420,7 +445,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param userAgent User agent * @return API client */ - public ApiClient setUserAgent(String userAgent) { + public final ApiClient setUserAgent(String userAgent) { addDefaultHeader("User-Agent", userAgent); return this; } @@ -442,7 +467,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param value The header's value * @return API client */ - public ApiClient addDefaultHeader(String key, String value) { + public final ApiClient addDefaultHeader(String key, String value) { defaultHeaderMap.put(key, value); return this; } @@ -598,11 +623,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param value The value of the parameter. * @return A list of {@code Pair} objects. */ - public List parameterToPairs(String collectionFormat, String name, Collection value) { + public List parameterToPairs(String collectionFormat, String name, Collection value) { List params = new ArrayList(); // preconditions - if (name == null || name.isEmpty() || value == null) { + if (name == null || name.isEmpty() || value == null || value.isEmpty()) { return params; } @@ -732,10 +757,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** * Parse content type object from header value */ - private ContentType getContentType(String headerValue) throws ApiException { + protected ContentType getContentType(String headerValue) throws ApiException { try { return ContentType.parse(headerValue); - } catch (ParseException e) { + } catch (UnsupportedCharsetException e) { throw new ApiException("Could not parse content type " + headerValue); } } @@ -743,7 +768,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** * Get content type of a response or null if one was not provided */ - private String getResponseMimeType(HttpResponse response) throws ApiException { + protected String getResponseMimeType(HttpResponse response) throws ApiException { Header contentTypeHeader = response.getFirstHeader("Content-Type"); if (contentTypeHeader != null) { return getContentType(contentTypeHeader.getValue()).getMimeType(); @@ -764,7 +789,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { String mimeType = contentType.getMimeType(); if (isJsonMime(mimeType)) { try { - return new StringEntity(objectMapper.writeValueAsString(obj), contentType); + return new StringEntity(objectMapper.writeValueAsString(obj), contentType.withCharset(StandardCharsets.UTF_8)); } catch (JsonProcessingException e) { throw new ApiException(e); } @@ -816,7 +841,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @throws IOException IO exception */ @SuppressWarnings("unchecked") - public T deserialize(HttpResponse response, TypeReference valueType) throws ApiException, IOException { + public T deserialize(CloseableHttpResponse response, TypeReference valueType) throws ApiException, IOException, ParseException { if (valueType == null) { return null; } @@ -830,22 +855,29 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { String mimeType = getResponseMimeType(response); if (mimeType == null || isJsonMime(mimeType)) { // Assume json if no mime type - return objectMapper.readValue(entity.getContent(), valueType); - } else if ("text/plain".equalsIgnoreCase(mimeType)) { // convert input stream to string - java.util.Scanner s = new java.util.Scanner(entity.getContent()).useDelimiter("\\A"); - return (T) (s.hasNext() ? s.next() : ""); + String content = EntityUtils.toString(entity); + + if ("".equals(content)) { // returns null for empty body + return null; + } + + return objectMapper.readValue(content, valueType); + } else if (mimeType.toLowerCase().startsWith("text/")) { + // convert input stream to string + return (T) EntityUtils.toString(entity); } else { + Map> responseHeaders = transformResponseHeaders(response.getHeaders()); throw new ApiException( "Deserialization for content type '" + mimeType + "' not supported for type '" + valueType + "'", - response.getStatusLine().getStatusCode(), + response.getCode(), responseHeaders, EntityUtils.toString(entity) ); } } - private File downloadFileFromResponse(HttpResponse response) throws IOException { + protected File downloadFileFromResponse(CloseableHttpResponse response) throws IOException { Header contentDispositionHeader = response.getFirstHeader("Content-Disposition"); String contentDisposition = contentDispositionHeader == null ? null : contentDispositionHeader.getValue(); File file = prepareDownloadFile(contentDisposition); @@ -888,18 +920,16 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } /** - * Build full URL by concatenating base path, the given sub path and query parameters. + * Returns the URL of the client as defined by the server (if exists) or the base path. * - * @param path The sub path - * @param queryParams The query parameters - * @param collectionQueryParams The collection query parameters - * @return The full URL + * @return The URL for the client. */ - private String buildUrl(String path, List queryParams, List collectionQueryParams) { + public String getBaseURL() { String baseURL; if (serverIndex != null) { if (serverIndex < 0 || serverIndex >= servers.size()) { throw new ArrayIndexOutOfBoundsException(String.format( + java.util.Locale.ROOT, "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() )); } @@ -907,6 +937,20 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } else { baseURL = basePath; } + return baseURL; + } + + /** + * Build full URL by concatenating base URL, the given sub path and query parameters. + * + * @param path The sub path + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param urlQueryDeepObject URL query string of the deep object parameters + * @return The full URL + */ + protected String buildUrl(String path, List queryParams, List collectionQueryParams, String urlQueryDeepObject) { + String baseURL = getBaseURL(); final StringBuilder url = new StringBuilder(); url.append(baseURL).append(path); @@ -946,6 +990,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } + if (urlQueryDeepObject != null && urlQueryDeepObject.length() > 0) { + url.append(url.toString().contains("?") ? "&" : "?"); + url.append(urlQueryDeepObject); + } + return url.toString(); } @@ -964,13 +1013,16 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return cookie; } - protected T processResponse(CloseableHttpResponse response, TypeReference returnType) throws ApiException, IOException { - statusCode = response.getStatusLine().getStatusCode(); + protected T processResponse(CloseableHttpResponse response, TypeReference returnType) throws ApiException, IOException, ParseException { + int statusCode = response.getCode(); + lastStatusCode.set(statusCode); if (statusCode == HttpStatus.SC_NO_CONTENT) { return null; } - responseHeaders = transformResponseHeaders(response.getAllHeaders()); + Map> responseHeaders = transformResponseHeaders(response.getHeaders()); + lastResponseHeaders.set(responseHeaders); + if (isSuccessfulStatus(statusCode)) { return this.deserialize(response, returnType); } else { @@ -987,6 +1039,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" * @param queryParams The query parameters * @param collectionQueryParams The collection query parameters + * @param urlQueryDeepObject A URL query string for deep object parameters * @param body The request body object - if it is not binary, otherwise null * @param headerParams The header parameters * @param cookieParams The cookie parameters @@ -1003,6 +1056,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { String method, List queryParams, List collectionQueryParams, + String urlQueryDeepObject, Object body, Map headerParams, Map cookieParams, @@ -1016,16 +1070,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); - final String url = buildUrl(path, queryParams, collectionQueryParams); + final String url = buildUrl(path, queryParams, collectionQueryParams, urlQueryDeepObject); - RequestBuilder builder = RequestBuilder.create(method); + ClassicRequestBuilder builder = ClassicRequestBuilder.create(method); builder.setUri(url); - RequestConfig config = RequestConfig.custom() - .setConnectionRequestTimeout(connectionTimeout) - .build(); - builder.setConfig(config); - if (accept != null) { builder.addHeader("Accept", accept); } @@ -1061,12 +1110,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } else { // for empty body - builder.setEntity(serialize(null, null, contentTypeObj)); + builder.setEntity(new StringEntity("", contentTypeObj)); } try (CloseableHttpResponse response = httpClient.execute(builder.build(), context)) { return processResponse(response, returnType); - } catch (IOException e) { + } catch (IOException | ParseException e) { throw new ApiException(e); } } @@ -1079,7 +1128,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param headerParams Header parameters * @param cookieParams Cookie parameters */ - private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { + protected 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); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/BaseApi.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/BaseApi.mustache new file mode 100644 index 000000000..f046b1995 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/BaseApi.mustache @@ -0,0 +1,111 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.Collections; +import java.util.Map; + +{{>generatedAnnotation}} + +public abstract class BaseApi { + + protected ApiClient apiClient; + + public BaseApi() { + this(Configuration.getDefaultApiClient()); + } + + public BaseApi(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public ApiClient getApiClient() { + return apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @throws ApiException if fails to make API call. + */ + public void invokeAPI(String url, String method) throws ApiException { + invokeAPI(url, method, null, null, Collections.emptyMap()); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param additionalHeaders Additional headers for the request. + * @throws ApiException if fails to make API call. + */ + public void invokeAPI(String url, String method, Map additionalHeaders) throws ApiException { + invokeAPI(url, method, null, null, additionalHeaders); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @throws ApiException if fails to make API call. + */ + public void invokeAPI(String url, String method, Object request) throws ApiException { + invokeAPI(url, method, request, null, Collections.emptyMap()); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @param additionalHeaders Additional headers for the request. + * @throws ApiException if fails to make API call. + */ + public void invokeAPI(String url, String method, Object request, Map additionalHeaders) throws ApiException { + invokeAPI(url, method, request, null, additionalHeaders); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param returnType The return type. + * @return The API response in the specified type. + * @throws ApiException if fails to make API call. + */ + public T invokeAPI(String url, String method, TypeReference returnType) throws ApiException { + return invokeAPI(url, method, null, returnType, Collections.emptyMap()); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @param returnType The return type. + * @return The API response in the specified type. + * @throws ApiException if fails to make API call. + */ + public T invokeAPI(String url, String method, Object request, TypeReference returnType) throws ApiException { + return invokeAPI(url, method, request, returnType, Collections.emptyMap()); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @param returnType The return type. + * @param additionalHeaders Additional headers for the request. + * @return The API response in the specified type. + * @throws ApiException if fails to make API call. + */ + public abstract T invokeAPI(String url, String method, Object request, TypeReference returnType, Map additionalHeaders) throws ApiException; +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/README.mustache index c6ae4fa23..b50e7db08 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/README.mustache @@ -8,6 +8,8 @@ - Build date: {{generatedDate}} {{/hideGenerationTimestamp}} +- Generator version: {{generatorVersion}} + {{#appDescriptionWithNewLines}}{{{appDescriptionWithNewLines}}}{{/appDescriptionWithNewLines}} {{#infoUrl}} @@ -158,11 +160,14 @@ Class | Method | HTTP request | Description {{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) {{/model}}{{/models}} + ## Documentation for Authorization -{{^authMethods}}All endpoints do not require authorization. -{{/authMethods}}Authentication schemes defined for the API: -{{#authMethods}}### {{name}} +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{name}} {{#isApiKey}} @@ -170,10 +175,18 @@ Class | Method | HTTP request | Description - **API key parameter name**: {{keyParamName}} - **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} {{/isApiKey}} -{{#isBasic}} +{{#isBasicBasic}} - **Type**: HTTP basic authentication -{{/isBasic}} +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} {{#isOAuth}} - **Type**: OAuth diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api.mustache index 772878710..289adc98c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api.mustache @@ -5,49 +5,76 @@ import com.fasterxml.jackson.core.type.TypeReference; import {{invokerPackage}}.ApiException; import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.BaseApi; import {{invokerPackage}}.Configuration; +{{#models.0}} import {{modelPackage}}.*; +{{/models.0}} import {{invokerPackage}}.Pair; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +import java.util.StringJoiner; +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} {{>generatedAnnotation}} + {{#operations}} -public class {{classname}} { - private ApiClient apiClient; +public class {{classname}} extends BaseApi { public {{classname}}() { - this(Configuration.getDefaultApiClient()); + super(Configuration.getDefaultApiClient()); } public {{classname}}(ApiClient apiClient) { - this.apiClient = apiClient; + super(apiClient); } - public ApiClient getApiClient() { - return apiClient; + {{#operation}} + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} + {{/allParams}} + {{#returnType}} + * @return {{returnType}} + {{/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}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#returnType}}return {{/returnType}}this.{{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}Collections.emptyMap()); } - 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}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} {{/allParams}} + * @param additionalHeaders additionalHeaders for this call {{#returnType}} * @return {{returnType}} {{/returnType}} @@ -63,7 +90,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}Map additionalHeaders) throws ApiException { Object localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; {{#allParams}}{{#required}} // verify the required parameter '{{paramName}}' is set @@ -73,18 +100,27 @@ public class {{classname}} { {{/required}}{{/allParams}} // create path and map variables String localVarPath = "{{{path}}}"{{#pathParams}} - .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString(apiClient.parameterToString({{{paramName}}}))){{/pathParams}}; - // query params - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}List localVarCollectionQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + String localVarQueryParameterBaseName; + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); {{#queryParams}} {{#isDeepObject}} - {{#collectionFormat}}localVarCollectionQueryParams.addAll(apiClient.parameterToPairs("{{{collectionFormat}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); + localVarQueryParameterBaseName = "{{{baseName}}}"; + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + localVarQueryStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format(java.util.Locale.ROOT, "{{baseName}}[%d]", i))); + } + {{/isArray}} + {{^isArray}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString("{{baseName}}")); + {{/isArray}} {{/isDeepObject}} {{^isDeepObject}} {{#isExplode}} @@ -99,7 +135,12 @@ public class {{classname}} { {{/vars}} {{/hasVars}} {{^hasVars}} + {{#isModel}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString()); + {{/isModel}} + {{^isModel}} {{#collectionFormat}}localVarCollectionQueryParams.addAll(apiClient.parameterToPairs("{{{collectionFormat}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); + {{/isModel}} {{/hasVars}} {{/isExplode}} {{^isExplode}} @@ -111,6 +152,8 @@ public class {{classname}} { localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); {{/headerParams}} + localVarHeaderParams.putAll(additionalHeaders); + {{#cookieParams}}if ({{paramName}} != null) localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); {{/cookieParams}} @@ -142,6 +185,7 @@ public class {{classname}} { "{{httpMethod}}", localVarQueryParams, localVarCollectionQueryParams, + localVarQueryStringJoiner.toString(), localVarPostBody, localVarHeaderParams, localVarCookieParams, @@ -152,6 +196,49 @@ public class {{classname}} { {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}} ); } + + {{#-last}} + @Override + public T invokeAPI(String url, String method, Object request, TypeReference returnType, Map additionalHeaders) throws ApiException { + String localVarPath = url.replace(apiClient.getBaseURL(), ""); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + localVarHeaderParams.putAll(additionalHeaders); + + 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}} }; + + return apiClient.invokeAPI( + localVarPath, + method, + localVarQueryParams, + localVarCollectionQueryParams, + localVarQueryStringJoiner.toString(), + request, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + returnType + ); + } + {{/-last}} {{/operation}} } {{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api_test.mustache index ca6173a61..05b2bf9fd 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/api_test.mustache @@ -5,23 +5,26 @@ package {{package}}; import {{invokerPackage}}.ApiException; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ -@Ignore +@Disabled public class {{classname}}Test { private final {{classname}} api = new {{classname}}(); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/build.gradle.mustache index 6c7379be3..03dd253cb 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -97,12 +97,12 @@ if(hasProperty('target') && target == 'android') { } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -114,13 +114,13 @@ if(hasProperty('target') && target == 'android') { ext { swagger_annotations_version = "1.6.6" - jackson_version = "2.14.1" - jackson_databind_version = "2.14.1" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" - httpclient_version = "4.5.13" + httpclient_version = "5.1.3" jodatime_version = "2.9.9" junit_version = "4.13.2" } @@ -128,8 +128,7 @@ ext { dependencies { implementation "io.swagger:swagger-annotations:$swagger_annotations_version" implementation "com.google.code.findbugs:jsr305:3.0.2" - implementation "org.apache.httpcomponents:httpclient:$httpclient_version" - implementation "org.apache.httpcomponents:httpmime:$httpclient_version" + implementation "org.apache.httpcomponents.client5:httpclient5:$httpclient_version" 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_databind_version" diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/pom.mustache index 6e5abf83f..af8cf6b71 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/apache-httpclient/pom.mustache @@ -80,18 +80,17 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + 3.2.5 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods 10 - pertest @@ -221,6 +220,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -232,13 +238,8 @@ - org.apache.httpcomponents - httpclient - ${httpclient-version} - - - org.apache.httpcomponents - httpmime + org.apache.httpcomponents.client5 + httpclient5 ${httpclient-version} @@ -258,58 +259,67 @@ jackson-databind ${jackson-databind-version} + {{^useJakartaEe}} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider ${jackson-version} + {{/useJakartaEe}} + {{#useJakartaEe}} + + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-json-provider + ${jackson-version} + + {{/useJakartaEe}} {{#withXml}} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${jackson-version} - + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + {{/withXml}} {{#joda}} - - com.fasterxml.jackson.datatype - jackson-datatype-joda - ${jackson-version} - + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + {{/joda}} - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson-version} - + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + {{#useBeanValidation}} - - - jakarta.validation - jakarta.validation-api - ${beanvalidation-version} - provided - + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + {{/useBeanValidation}} {{#performBeanValidation}} - - - org.hibernate - hibernate-validator - 5.4.3.Final - + + + org.hibernate + hibernate-validator + 5.4.3.Final + {{/performBeanValidation}} {{#parcelableModel}} - - - com.google.android - android - 4.1.1.4 - provided - + + + com.google.android + android + 4.1.1.4 + provided + {{/parcelableModel}} {{#openApiNullable}} @@ -326,8 +336,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test @@ -337,16 +347,23 @@ {{#swagger1AnnotationLibrary}} 1.6.6 {{/swagger1AnnotationLibrary}} - 4.5.13 - 2.14.1 - 2.14.1 + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 5.2.1 + 2.19.2 + 2.19.2 {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} - 1.3.5 - {{#useBeanValidation}} + {{#useJakartaEe}} + 2.1.1 3.0.2 - {{/useBeanValidation}} - 4.13.2 + {{/useJakartaEe}} + {{^useJakartaEe}} + 1.3.5 + 2.0.2 + {{/useJakartaEe}} + 5.10.2 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiClient.mustache index cc9599940..3e8a99abd 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiClient.mustache @@ -1,33 +1,52 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; -import feign.okhttp.OkHttpClient; - +{{#jackson}} import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; {{#openApiNullable}} import org.openapitools.jackson.nullable.JsonNullableModule; {{/openApiNullable}} +{{/jackson}} {{#joda}} import com.fasterxml.jackson.datatype.joda.JodaModule; {{/joda}} +{{#jackson}} import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{/jackson}} import feign.Feign; import feign.RequestInterceptor; import feign.form.FormEncoder; +{{#jackson}} import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; +{{/jackson}} +{{#gson}} +import feign.gson.GsonDecoder; +import feign.gson.GsonEncoder; +{{/gson}} +{{#feign-okhttp}} +import feign.okhttp.OkHttpClient; +{{/feign-okhttp}} +{{#feign-hc5}} +import feign.hc5.ApacheHttp5Client; +{{/feign-hc5}} import feign.slf4j.Slf4jLogger; import {{invokerPackage}}.auth.HttpBasicAuth; import {{invokerPackage}}.auth.HttpBearerAuth; import {{invokerPackage}}.auth.ApiKeyAuth; +{{#jackson}} import {{invokerPackage}}.ApiResponseDecoder; +{{/jackson}} {{#hasOAuthMethods}} import {{invokerPackage}}.auth.ApiErrorDecoder; @@ -40,21 +59,36 @@ import feign.Retryer; {{/hasOAuthMethods}} {{>generatedAnnotation}} + public class ApiClient { - private static final Logger log = Logger.getLogger(ApiClient.class.getName()); + protected static final Logger log = Logger.getLogger(ApiClient.class.getName()); public interface Api {} + {{#jackson}} protected ObjectMapper objectMapper; - private String basePath = "{{{basePath}}}"; - private Map apiAuthorizations; - private Feign.Builder feignBuilder; + {{/jackson}} + protected String basePath = "{{{basePath}}}"; + protected Map apiAuthorizations; + protected Feign.Builder feignBuilder; + {{#feign-okhttp}} + private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient(); + {{/feign-okhttp}} + {{#feign-hc5}} + private static final ApacheHttp5Client APACHE_HTTP5_CLIENT = new ApacheHttp5Client(); + {{/feign-hc5}} public ApiClient() { - objectMapper = createObjectMapper(); apiAuthorizations = new LinkedHashMap(); + {{#jackson}} + objectMapper = createObjectMapper(); feignBuilder = Feign.builder() - .client(new OkHttpClient()) + {{#feign-okhttp}} + .client(OK_HTTP_CLIENT) + {{/feign-okhttp}} + {{#feign-hc5}} + .client(APACHE_HTTP5_CLIENT) + {{/feign-hc5}} .encoder(new FormEncoder(new JacksonEncoder(objectMapper))) .decoder(new ApiResponseDecoder(objectMapper)) {{#hasOAuthMethods}} @@ -62,6 +96,23 @@ public class ApiClient { .retryer(new Retryer.Default(0, 0, 2)) {{/hasOAuthMethods}} .logger(new Slf4jLogger()); + {{/jackson}} + {{#gson}} + feignBuilder = Feign.builder() + {{#feign-okhttp}} + .client(OK_HTTP_CLIENT) + {{/feign-okhttp}} + {{#feign-hc5}} + .client(APACHE_HTTP5_CLIENT) + {{/feign-hc5}} + .encoder(new FormEncoder(new GsonEncoder())) + .decoder(new GsonDecoder()) + {{#hasOAuthMethods}} + .errorDecoder(new ApiErrorDecoder()) + .retryer(new Retryer.Default(0, 0, 2)) + {{/hasOAuthMethods}} + .logger(new Slf4jLogger()); + {{/gson}} } public ApiClient(String[] authNames) { @@ -69,16 +120,14 @@ public class ApiClient { for(String authName : authNames) { log.log(Level.FINE, "Creating authentication {0}", authName); {{#hasAuthMethods}} - RequestInterceptor auth; + RequestInterceptor auth = null; {{#authMethods}}if ("{{name}}".equals(authName)) { - {{#isBasic}} {{#isBasicBasic}} auth = new HttpBasicAuth(); {{/isBasicBasic}} - {{^isBasicBasic}} + {{#isBasicBearer}} auth = new HttpBearerAuth("{{scheme}}"); - {{/isBasicBasic}} - {{/isBasic}} + {{/isBasicBearer}} {{#isApiKey}} auth = new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"); {{/isApiKey}} @@ -88,7 +137,9 @@ public class ApiClient { } else {{/authMethods}}{ throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); } - addAuthorization(authName, auth); + if (auth != null) { + addAuthorization(authName, auth); + } {{/hasAuthMethods}} {{^hasAuthMethods}} throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); @@ -140,11 +191,17 @@ public class ApiClient { return this; } - private ObjectMapper createObjectMapper() { + {{#jackson}} + protected ObjectMapper createObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + {{#failOnUnknownProperties}} + objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + {{/failOnUnknownProperties}} + {{^failOnUnknownProperties}} objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + {{/failOnUnknownProperties}} objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.setDateFormat(new RFC3339DateFormat()); @@ -152,15 +209,17 @@ public class ApiClient { objectMapper.registerModule(new JodaModule()); {{/joda}} objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule(new RFC3339JavaTimeModule()); {{#openApiNullable}} JsonNullableModule jnm = new JsonNullableModule(); objectMapper.registerModule(jnm); {{/openApiNullable}} return objectMapper; } + {{/jackson}} {{#hasOAuthMethods}} - private RequestInterceptor buildOauthRequestInterceptor(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { + protected RequestInterceptor buildOauthRequestInterceptor(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { switch (flow) { case PASSWORD: return new OauthPasswordGrant(tokenUrl, scopes); @@ -172,6 +231,8 @@ public class ApiClient { } {{/hasOAuthMethods}} + + {{#jackson}} public ObjectMapper getObjectMapper(){ return objectMapper; } @@ -179,6 +240,7 @@ public class ApiClient { public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } + {{/jackson}} /** * Creates a feign client for given API interface. @@ -235,6 +297,15 @@ public class ApiClient { apiAuthorization.setBearerToken(bearerToken); } + /** + * Helper method to configure the supplier of bearer tokens. + * @param tokenSupplier the supplier of bearer tokens. + */ + public void setBearerToken(Supplier tokenSupplier) { + HttpBearerAuth apiAuthorization = getAuthorization(HttpBearerAuth.class); + apiAuthorization.setBearerToken(tokenSupplier); + } + /** * Helper method to configure the first api key found * @param apiKey API key @@ -329,7 +400,7 @@ public class ApiClient { feignBuilder.requestInterceptor(authorization); } - private T getAuthorization(Class type) { + protected T getAuthorization(Class type) { return (T) apiAuthorizations.values() .stream() .filter(requestInterceptor -> type.isAssignableFrom(requestInterceptor.getClass())) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiResponseDecoder.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiResponseDecoder.mustache index 2ff7a3243..7eaf68cea 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiResponseDecoder.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ApiResponseDecoder.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.databind.ObjectMapper; @@ -5,34 +7,99 @@ import feign.Response; import feign.Types; import feign.jackson.JacksonDecoder; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import {{modelPackage}}.ApiResponse; public class ApiResponseDecoder extends JacksonDecoder { + private static final Pattern FILENAME_PATTERN = + Pattern.compile("filename=\"([^\"]+)\"|filename=([^\\s;]+)"); + public ApiResponseDecoder(ObjectMapper mapper) { super(mapper); } @Override public Object decode(Response response, Type type) throws IOException { - Map> responseHeaders = Collections.unmodifiableMap(response.headers()); - //Detects if the type is an instance of the parameterized class ApiResponse - Type responseBodyType; if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) { - //The ApiResponse class has a single type parameter, the Dto class itself - responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0]; - Object body = super.decode(response, responseBodyType); - return new ApiResponse(response.status(), responseHeaders, body); + Type responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0]; + Object body = isBinaryType(responseBodyType) + ? decodeBinary(response, responseBodyType) + : super.decode(response, responseBodyType); + Map> responseHeaders = Collections.unmodifiableMap(response.headers()); + return new ApiResponse<>(response.status(), responseHeaders, body); + } + + if (isBinaryType(type)) { + return decodeBinary(response, type); + } + + return super.decode(response, type); + } + + private boolean isBinaryType(Type type) { + Class raw = Types.getRawType(type); + return File.class.isAssignableFrom(raw) + || byte[].class.isAssignableFrom(raw) + || InputStream.class.isAssignableFrom(raw); + } + + private Object decodeBinary(Response response, Type type) throws IOException { + Class raw = Types.getRawType(type); + if (response.body() == null) { + return null; + } + if (byte[].class.isAssignableFrom(raw)) { + return response.body().asInputStream().readAllBytes(); + } + if (InputStream.class.isAssignableFrom(raw)) { + return response.body().asInputStream(); + } + return downloadToTempFile(response); + } + + private File downloadToTempFile(Response response) throws IOException { + String filename = extractFilename(response); + File file; + if (filename != null) { + // Sanitize: strip path components to prevent path traversal + String safeName = Paths.get(filename).getFileName().toString(); + java.nio.file.Path tempDir = Files.createTempDirectory("feign-download"); + file = Files.createFile(tempDir.resolve(safeName)).toFile(); + tempDir.toFile().deleteOnExit(); } else { - //The response is not encapsulated in the ApiResponse, decode the Dto as normal - return super.decode(response, type); + file = Files.createTempFile("download-", "").toFile(); + } + file.deleteOnExit(); + try (InputStream is = response.body().asInputStream()) { + Files.copy(is, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + return file; + } + + private String extractFilename(Response response) { + Collection dispositions = response.headers().get("Content-Disposition"); + if (dispositions == null) return null; + for (String disposition : dispositions) { + Matcher m = FILENAME_PATTERN.matcher(disposition); + if (m.find()) { + // Group 1: quoted filename (may contain spaces), Group 2: unquoted token + return m.group(1) != null ? m.group(1) : m.group(2); + } } + return null; } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/EncodingUtils.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/EncodingUtils.mustache index 705eb6aa9..2312fc631 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/EncodingUtils.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/EncodingUtils.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.io.UnsupportedEncodingException; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ParamExpander.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ParamExpander.mustache index 2f5095d00..88f0ae22c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/ParamExpander.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/ParamExpander.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import feign.Param; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/README.mustache index fed3cbebd..c3d948749 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/README.mustache @@ -32,7 +32,7 @@ After the client library is installed/deployed, you can use it in your Maven pro ``` -And to use the api you can follow the examples bellow: +And to use the api you can follow the examples below: ```java diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/additional_properties.mustache new file mode 100644 index 000000000..8e7182792 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/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/boat-scaffold/src/main/templates/boat-java/libraries/feign/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/api.mustache index d7789e6fe..0ec95e7a8 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/api.mustache @@ -10,15 +10,19 @@ import {{modelPackage}}.ApiResponse; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} import feign.*; {{>generatedAnnotation}} + public interface {{classname}} extends ApiClient.Api { {{#operations}}{{#operation}} @@ -44,12 +48,12 @@ public interface {{classname}} extends ApiClient.Api { {{/isDeprecated}} @RequestLine("{{httpMethod}} {{{path}}}{{#hasQueryParams}}?{{/hasQueryParams}}{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}") @Headers({ -{{#vendorExtensions.x-content-type}} "Content-Type: {{vendorExtensions.x-content-type}}", -{{/vendorExtensions.x-content-type}} "Accept: {{vendorExtensions.x-accepts}}",{{#headerParams}} +{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}", +{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}} "{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}}, {{/-last}}{{/headerParams}} }) - {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); /** * {{summary}} @@ -74,12 +78,12 @@ public interface {{classname}} extends ApiClient.Api { {{/isDeprecated}} @RequestLine("{{httpMethod}} {{{path}}}{{#hasQueryParams}}?{{/hasQueryParams}}{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}") @Headers({ -{{#vendorExtensions.x-content-type}} "Content-Type: {{vendorExtensions.x-content-type}}", -{{/vendorExtensions.x-content-type}} "Accept: {{vendorExtensions.x-accepts}}",{{#headerParams}} +{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}", +{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}} "{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}}, {{/-last}}{{/headerParams}} }) - ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); {{#hasQueryParams}} @@ -119,12 +123,12 @@ public interface {{classname}} extends ApiClient.Api { {{/isDeprecated}} @RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}") @Headers({ -{{#vendorExtensions.x-content-type}} "Content-Type: {{vendorExtensions.x-content-type}}", -{{/vendorExtensions.x-content-type}} "Accept: {{vendorExtensions.x-accepts}}",{{#headerParams}} +{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}", +{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}} "{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}}, {{/-last}}{{/headerParams}} }) - {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map queryParams); + {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) {{operationIdCamelCase}}QueryParams queryParams); /** * {{summary}} @@ -159,12 +163,12 @@ public interface {{classname}} extends ApiClient.Api { {{/isDeprecated}} @RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}") @Headers({ - {{#vendorExtensions.x-content-type}} "Content-Type: {{vendorExtensions.x-content-type}}", - {{/vendorExtensions.x-content-type}} "Accept: {{vendorExtensions.x-accepts}}",{{#headerParams}} + {{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}", + {{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}} "{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}}, {{/-last}}{{/headerParams}} }) - ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map queryParams); + ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) {{operationIdCamelCase}}QueryParams queryParams); /** @@ -173,7 +177,7 @@ public interface {{classname}} extends ApiClient.Api { */ public static class {{operationIdCamelCase}}QueryParams extends HashMap { {{#queryParams}} - public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) { + public {{operationIdCamelCase}}QueryParams {{paramName}}({{>nullable_var_annotations}} final {{{dataType}}} value) { {{#collectionFormat}} put("{{baseName}}", EncodingUtils.encodeCollection(value, "{{collectionFormat}}")); {{/collectionFormat}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/api_test.mustache index c579a5c9d..62521123f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/api_test.mustache @@ -8,13 +8,16 @@ import org.junit.jupiter.api.BeforeEach; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiErrorDecoder.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiErrorDecoder.mustache index da87f2563..aeea7f97e 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiErrorDecoder.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiErrorDecoder.mustache @@ -1,5 +1,9 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; +import java.util.Date; + import feign.Response; import feign.RetryableException; import feign.codec.ErrorDecoder; @@ -18,7 +22,7 @@ public class ApiErrorDecoder implements ErrorDecoder { Exception httpException = defaultErrorDecoder.decode(methodKey, response); if (response.status() == 401 || response.status() == 403) { return new RetryableException(response.status(), "Received status " + response.status() + " trying to renew access token", - response.request().httpMethod(), httpException, null, response.request()); + response.request().httpMethod(), httpException, (Date) null, response.request()); } return httpException; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiKeyAuth.mustache index c03fe5c0b..4a8da9e53 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/ApiKeyAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import feign.RequestInterceptor; @@ -37,7 +39,7 @@ public class ApiKeyAuth implements RequestInterceptor { } else if ("header".equals(location)) { template.header(paramName, apiKey); } else if ("cookie".equals(location)) { - template.header("Cookie", String.format("%s=%s", paramName, apiKey)); + template.header("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", paramName, apiKey)); } } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/DefaultApi20Impl.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/DefaultApi20Impl.mustache index 72b0a0049..9fee98bd0 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/DefaultApi20Impl.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/DefaultApi20Impl.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import com.github.scribejava.core.builder.api.DefaultApi20; @@ -10,6 +12,7 @@ import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthenticati import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme; {{>generatedAnnotation}} + public class DefaultApi20Impl extends DefaultApi20 { private final String accessTokenEndpoint; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBasicAuth.mustache index c308131e8..d95589b6f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBasicAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import feign.RequestInterceptor; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBearerAuth.mustache index 2240a5518..466c1b2ad 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/HttpBearerAuth.mustache @@ -1,14 +1,18 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import feign.RequestInterceptor; import feign.RequestTemplate; +import java.util.Optional; +import java.util.function.Supplier; /** * An interceptor that adds the request header needed to use HTTP bearer authentication. */ public class HttpBearerAuth implements RequestInterceptor { private final String scheme; - private String bearerToken; + private Supplier tokenSupplier; public HttpBearerAuth(String scheme) { this.scheme = scheme; @@ -16,21 +20,35 @@ public class HttpBearerAuth implements RequestInterceptor { /** * 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; + return tokenSupplier.get(); } /** * 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; + this.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; } @Override public void apply(RequestTemplate template) { - if(bearerToken == null) { + String bearerToken = Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); + if (bearerToken == null) { return; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OAuth.mustache index 5416da238..1baa48c1a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import com.github.scribejava.core.model.OAuth2AccessToken; @@ -8,6 +10,7 @@ import feign.RequestTemplate; import java.util.Collection; {{>generatedAnnotation}} + public abstract class OAuth implements RequestInterceptor { //https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthClientCredentialsGrant.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthClientCredentialsGrant.mustache index 2180273d0..95a6195a9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthClientCredentialsGrant.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthClientCredentialsGrant.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.OAuth2AccessToken; {{>generatedAnnotation}} + public class OauthClientCredentialsGrant extends OAuth { public OauthClientCredentialsGrant(String authorizationUrl, String tokenUrl, String scopes) { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthPasswordGrant.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthPasswordGrant.mustache index 522afa08e..cc3ceb8ef 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthPasswordGrant.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/auth/OauthPasswordGrant.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.OAuth2AccessToken; {{>generatedAnnotation}} + public class OauthPasswordGrant extends OAuth { private String username; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.gradle.mustache index bb35f8689..4186e0a0f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -101,46 +101,63 @@ test { } ext { - swagger_annotations_version = "1.5.24" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + swagger_annotations_version = "1.6.11" + {{#jackson}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" + {{/jackson}} {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" - feign_version = "10.11" + feign_version = "13.5" feign_form_version = "3.8.0" junit_version = "5.7.0" scribejava_version = "8.0.0" + {{#useReflectionEqualsHashCode}} + commons_lang3_version = "3.17.0" + {{/useReflectionEqualsHashCode}} } dependencies { implementation "io.swagger:swagger-annotations:$swagger_annotations_version" implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "io.github.openfeign:feign-core:$feign_version" + {{#jackson}} implementation "io.github.openfeign:feign-jackson:$feign_version" + {{/jackson}} implementation "io.github.openfeign:feign-slf4j:$feign_version" + {{#feign-okhttp}} implementation "io.github.openfeign:feign-okhttp:$feign_version" + {{/feign-okhttp}} + {{#feign-hc5}} + implementation "io.github.openfeign:feign-hc5:$feign_version" + {{/feign-hc5}} implementation "io.github.openfeign.form:feign-form:$feign_form_version" + {{#jackson}} + {{#joda}} + implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + {{/joda}} 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_databind_version" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/jackson}} {{#openApiNullable}} implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" {{/openApiNullable}} - {{#joda}} - implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" - {{/joda}} - implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" implementation "com.brsanthu:migbase64:2.2" implementation "com.github.scribejava:scribejava-core:$scribejava_version" implementation "com.brsanthu:migbase64:2.2" implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + {{#useReflectionEqualsHashCode}} + implementation "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/useReflectionEqualsHashCode}} testImplementation "org.junit.jupiter:junit-jupiter:$junit_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_version" - testImplementation "com.github.tomakehurst:wiremock-jre8:2.27.2" + testImplementation "com.github.tomakehurst:wiremock-jre8:2.35.1" testImplementation "org.hamcrest:hamcrest:2.2" - testImplementation "commons-io:commons-io:2.8.0" + testImplementation "commons-io:commons-io:2.16.1" testImplementation "ch.qos.logback:logback-classic:1.2.3" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.sbt.mustache index da07dcf61..ca859f159 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/build.sbt.mustache @@ -3,32 +3,44 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( - "io.swagger" % "swagger-annotations" % "1.5.24" % "compile", + "io.swagger" % "swagger-annotations" % "1.6.11" % "compile", "com.google.code.findbugs" % "jsr305" % "3.0.2" % "compile", - "io.github.openfeign" % "feign-core" % "10.11" % "compile", - "io.github.openfeign" % "feign-jackson" % "10.11" % "compile", - "io.github.openfeign" % "feign-slf4j" % "10.11" % "compile", + "io.github.openfeign" % "feign-core" % "13.5" % "compile", +{{#jackson}} + "io.github.openfeign" % "feign-jackson" % "13.5" % "compile", +{{/jackson}} + "io.github.openfeign" % "feign-slf4j" % "13.5" % "compile", "io.github.openfeign.form" % "feign-form" % "3.8.0" % "compile", - "io.github.openfeign" % "feign-okhttp" % "10.11" % "compile", - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.2" % "compile", - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.10" % "compile", - "com.github.joschi.jackson" % "jackson-datatype-threetenbp" % "2.9.10" % "compile", +{{#feign-okhttp}} + "io.github.openfeign" % "feign-okhttp" % "13.5" % "compile", +{{/feign-okhttp}} +{{#feign-hc5}} + "io.github.openfeign" % "feign-hc5" % "13.5" % "compile", +{{/feign-hc5}} +{{#jackson}} + "com.fasterxml.jackson.core" % "jackson-core" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.17.1" % "compile", + "com.github.joschi.jackson" % "jackson-datatype-threetenbp" % "2.15.2" % "compile", +{{/jackson}} "com.github.scribejava" % "scribejava-core" % "8.0.0" % "compile", "com.brsanthu" % "migbase64" % "2.2" % "compile", "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", +{{#useReflectionEqualsHashCode}} + "org.apache.commons" % "commons-lang3" % "3.17.0" % "compile", +{{/useReflectionEqualsHashCode}} "org.junit.jupiter" % "junit-jupiter" % "5.7.0" % "test", "org.junit.jupiter" % "junit-jupiter-params" % "5.7.0" % "test", - "com.github.tomakehurst" % "wiremock-jre8" % "2.27.2" % "test", + "com.github.tomakehurst" % "wiremock-jre8" % "2.35.1" % "test", "org.hamcrest" % "hamcrest" % "2.2" % "test", - "commons-io" % "commons-io" % "2.8.0" % "test", + "commons-io" % "commons-io" % "2.16.1" % "test", "com.novocode" % "junit-interface" % "0.10" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model.mustache new file mode 100644 index 000000000..108748f60 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model.mustache @@ -0,0 +1,78 @@ +{{>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}} +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/boat-scaffold/src/main/templates/boat-java/libraries/feign/model/ApiResponse.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model/ApiResponse.mustache index bc460dc59..9c9b10746 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/model/ApiResponse.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model/ApiResponse.mustache @@ -1,19 +1,21 @@ +{{>licenseInfo}} + package {{modelPackage}}; import java.util.Map; -import java.util.List; +import java.util.Collection; public class ApiResponse{ final private int statusCode; - final private Map> headers; + 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) { + public ApiResponse(int statusCode, Map> headers) { this(statusCode, headers, null); } @@ -22,7 +24,7 @@ public class ApiResponse{ * @param headers The headers of HTTP response * @param data The object deserialized from response bod */ - public ApiResponse(int statusCode, Map> headers, T data) { + public ApiResponse(int statusCode, Map> headers, T data) { this.statusCode = statusCode; this.headers = headers; this.data = data; @@ -32,7 +34,7 @@ public class ApiResponse{ return statusCode; } - public Map> getHeaders() { + public Map> getHeaders() { return headers; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/model_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model_test.mustache index 0d75e120b..2759ff525 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/model_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/model_test.mustache @@ -6,13 +6,6 @@ package {{package}}; {{/imports}} import org.junit.jupiter.api.Test; -{{#fullJavaUtil}} -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -{{/fullJavaUtil}} - /** * Model tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/feign/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/pojo.mustache new file mode 100644 index 000000000..515b37835 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/pojo.mustache @@ -0,0 +1,580 @@ +/** + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */{{#isDeprecated}} +@Deprecated{{/isDeprecated}} +{{#swagger1AnnotationLibrary}} +{{#description}} +@ApiModel(description = "{{{.}}}") +{{/description}} +{{/swagger1AnnotationLibrary}} +{{#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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{^isXmlAttribute}} + {{#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-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}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{#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}}({{>nullable_var_annotations}} {{{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{{nameInPascalCase}}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{{nameInPascalCase}}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}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#jsonb}} + @JsonbProperty("{{baseName}}") +{{/jsonb}} +{{#useBeanValidation}} +{{>beanValidation}} + +{{/useBeanValidation}} +{{#swagger1AnnotationLibrary}} + @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") +{{/swagger1AnnotationLibrary}} +{{#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}}({{>nullable_var_annotations}} {{{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}} +{{>libraries/feign/additional_properties}} + + @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({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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{{#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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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.isPrimitiveType}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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.isPrimitiveType}} + {{^items.isPrimitiveType}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + if ({{getter}}().get(_key) != null) { + joiner.add({{getter}}().get(_key).toUrlQueryString(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isPrimitiveType}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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/boat-scaffold/src/main/templates/boat-java/libraries/feign/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/feign/pom.mustache index 5a122b321..a5e1ee278 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/feign/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/feign/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 3.0.0-M4 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods 10 @@ -220,6 +220,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -234,11 +241,20 @@ feign-core ${feign-version} + {{#jackson}} io.github.openfeign feign-jackson ${feign-version} + {{/jackson}} + {{#gson}} + + io.github.openfeign + feign-gson + ${feign-version} + + {{/gson}} io.github.openfeign feign-slf4j @@ -249,12 +265,22 @@ feign-form ${feign-form-version} + {{#feign-okhttp}} io.github.openfeign feign-okhttp ${feign-version} + {{/feign-okhttp}} + {{#feign-hc5}} + + io.github.openfeign + feign-hc5 + ${feign-version} + + {{/feign-hc5}} + {{#jackson}} com.fasterxml.jackson.core @@ -271,6 +297,14 @@ jackson-databind ${jackson-databind-version} + {{/jackson}} + {{#gson}} + + com.google.code.gson + gson + ${gson-version} + + {{/gson}} {{#openApiNullable}} org.openapitools @@ -293,11 +327,13 @@ ${jackson-version} {{/joda}} + {{#jackson}} com.fasterxml.jackson.datatype jackson-datatype-jsr310 ${jackson-version} + {{/jackson}} com.github.scribejava scribejava-core @@ -309,12 +345,29 @@ ${jakarta-annotation-version} provided + {{#useBeanValidation}} + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + + {{/useBeanValidation}} + {{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + {{/useReflectionEqualsHashCode}} ch.qos.logback logback-classic - 1.2.10 + 1.3.13 test @@ -338,13 +391,7 @@ com.github.tomakehurst wiremock-jre8 - 2.27.2 - test - - - commons-io - commons-io - 2.8.0 + 2.35.1 test @@ -353,17 +400,37 @@ 1.8 ${java.version} ${java.version} - 1.6.6 - 10.11 + {{#swagger1AnnotationLibrary}} + 1.6.11 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 13.2.1 3.8.0 - 2.13.4 + {{#jackson}} + 2.17.1 + 2.17.1 + {{/jackson}} + {{#gson}} + 2.10.1 + {{/gson}} {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} - 2.13.4.2 + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 - 5.7.0 + 2.0.2 + {{/useJakartaEe}} + {{#useReflectionEqualsHashCode}} + 3.17.0 + {{/useReflectionEqualsHashCode}} + 5.10.0 1.0.0 - 8.0.0 + 8.3.3 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/ApiClient.mustache index c1b8e1fdd..32b3cae75 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import {{apiPackage}}.*; @@ -22,17 +24,23 @@ import java.io.IOException; import java.io.OutputStream; {{>generatedAnnotation}} + public class ApiClient { - private final String basePath; - private final HttpRequestFactory httpRequestFactory; - private final ObjectMapper objectMapper; + protected final String basePath; + protected final HttpRequestFactory httpRequestFactory; + protected final ObjectMapper objectMapper; - private static final String defaultBasePath = "{{basePath}}"; + protected static final String defaultBasePath = "{{basePath}}"; // A reasonable default object mapper. Client can pass in a chosen ObjectMapper anyway, this is just for reasonable defaults. - private static ObjectMapper createDefaultObjectMapper() { + protected static ObjectMapper createDefaultObjectMapper() { ObjectMapper objectMapper = new ObjectMapper() + {{#failOnUnknownProperties}} + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + {{/failOnUnknownProperties}} + {{^failOnUnknownProperties}} .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + {{/failOnUnknownProperties}} .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .setDateFormat(new RFC3339DateFormat()); {{#joda}} @@ -77,7 +85,7 @@ public class ApiClient { public class JacksonJsonHttpContent extends AbstractHttpContent { /* A POJO that can be serialized with a com.fasterxml Jackson ObjectMapper */ - private final Object data; + protected final Object data; public JacksonJsonHttpContent(Object data) { super(Json.MEDIA_TYPE); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api.mustache index f9af1a4a9..2b4365bbf 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api.mustache @@ -14,7 +14,7 @@ import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpResponse; import com.google.api.client.json.Json; -import javax.ws.rs.core.UriBuilder; +import {{javaxPackage}}.ws.rs.core.UriBuilder; import java.io.IOException; import java.util.Collection; import java.util.HashMap; @@ -23,6 +23,7 @@ import java.util.List; import java.util.ArrayList; {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { private ApiClient apiClient; @@ -61,7 +62,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws IOException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws IOException { {{#returnType}}HttpResponse response = {{/returnType}}{{operationId}}ForHttpResponse({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} TypeReference<{{{returnType}}}> typeRef = new TypeReference<{{{returnType}}}>() {}; return apiClient.getObjectMapper().readValue(response.getContent(), typeRef);{{/returnType}} @@ -85,7 +86,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#bodyParam}}{{^required}}{{{dataType}}} {{paramName}}, {{/required}}{{/bodyParam}}{{#requiredParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}, {{/hasRequiredParams}}Map params) throws IOException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#bodyParam}}{{^required}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/required}}{{/bodyParam}}{{#requiredParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}, {{/hasRequiredParams}}Map params) throws IOException { {{#returnType}}HttpResponse response = {{/returnType}}{{operationId}}ForHttpResponse({{#bodyParam}}{{^required}}{{paramName}}, {{/required}}{{/bodyParam}}{{#requiredParams}}{{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}, {{/hasRequiredParams}}params);{{#returnType}} TypeReference<{{{returnType}}}> typeRef = new TypeReference<{{{returnType}}}>() {}; return apiClient.getObjectMapper().readValue(response.getContent(), typeRef);{{/returnType}} @@ -94,7 +95,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public HttpResponse {{operationId}}ForHttpResponse({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws IOException { + public HttpResponse {{operationId}}ForHttpResponse({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws IOException { {{#allParams}}{{#required}}// verify the required parameter '{{paramName}}' is set if ({{paramName}} == null) { throw new IllegalArgumentException("Missing the required parameter '{{paramName}}' when calling {{operationId}}"); @@ -122,13 +123,19 @@ public class {{classname}} { GenericUrl genericUrl = new GenericUrl(localVarUrl); HttpContent content = {{#isBodyAllowed}}{{#bodyParam}}apiClient.new JacksonJsonHttpContent({{paramName}}){{/bodyParam}}{{^bodyParam}}new EmptyContent(){{/bodyParam}}{{/isBodyAllowed}}{{^isBodyAllowed}}null{{/isBodyAllowed}}; - return apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content).execute(); + com.google.api.client.http.HttpRequest httpRequest = apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content); + {{#headerParams}} + if ({{paramName}} != null) { + httpRequest.getHeaders().set("{{baseName}}", {{paramName}}); + } + {{/headerParams}} + return httpRequest.execute(); }{{#bodyParam}} {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public HttpResponse {{operationId}}ForHttpResponse({{#allParams}}{{#isBodyParam}}java.io.InputStream {{paramName}}{{/isBodyParam}}{{^isBodyParam}}{{{dataType}}} {{paramName}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}, String mediaType) throws IOException { + public HttpResponse {{operationId}}ForHttpResponse({{#allParams}}{{#isBodyParam}}java.io.InputStream {{paramName}}{{/isBodyParam}}{{^isBodyParam}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}, String mediaType) throws IOException { {{#allParams}}{{#required}}// verify the required parameter '{{paramName}}' is set if ({{paramName}} == null) { throw new IllegalArgumentException("Missing the required parameter '{{paramName}}' when calling {{operationId}}"); @@ -158,13 +165,19 @@ public class {{classname}} { HttpContent content = {{#bodyParam}}{{paramName}} == null ? apiClient.new JacksonJsonHttpContent(null) : new InputStreamContent(mediaType == null ? Json.MEDIA_TYPE : mediaType, {{paramName}}){{/bodyParam}}; - return apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content).execute(); + com.google.api.client.http.HttpRequest httpRequest = apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content); + {{#headerParams}} + if ({{paramName}} != null) { + httpRequest.getHeaders().set("{{baseName}}", {{paramName}}); + } + {{/headerParams}} + return httpRequest.execute(); }{{/bodyParam}} {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public HttpResponse {{operationId}}ForHttpResponse({{#bodyParam}}{{^required}}{{{dataType}}} {{paramName}}, {{/required}}{{/bodyParam}}{{#requiredParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}, {{/hasRequiredParams}}Map params) throws IOException { + public HttpResponse {{operationId}}ForHttpResponse({{#bodyParam}}{{^required}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/required}}{{/bodyParam}}{{#requiredParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}, {{/hasRequiredParams}}Map params) throws IOException { {{#allParams}}{{#required}}// verify the required parameter '{{paramName}}' is set if ({{paramName}} == null) { throw new IllegalArgumentException("Missing the required parameter '{{paramName}}' when calling {{operationId}}"); @@ -200,7 +213,21 @@ public class {{classname}} { GenericUrl genericUrl = new GenericUrl(localVarUrl); HttpContent content = {{#isBodyAllowed}}{{#bodyParam}}apiClient.new JacksonJsonHttpContent({{paramName}}){{/bodyParam}}{{^bodyParam}}new EmptyContent(){{/bodyParam}}{{/isBodyAllowed}}{{^isBodyAllowed}}null{{/isBodyAllowed}}; - return apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content).execute(); + com.google.api.client.http.HttpRequest httpRequest = apiClient.getHttpRequestFactory().buildRequest(HttpMethods.{{httpMethod}}, genericUrl, content); + {{#hasHeaderParams}} + // Note: Header params passed via 'params' map are handled below + for (Map.Entry entry: params.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // Check if this is a header parameter by name + {{#headerParams}} + if ("{{baseName}}".equals(key) && value != null) { + httpRequest.getHeaders().set(key, value); + } + {{/headerParams}} + } + {{/hasHeaderParams}} + return httpRequest.execute(); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api_test.mustache index e81a6cf29..bfb939f95 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/api_test.mustache @@ -4,28 +4,26 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} /** * API tests for {{classname}} */ -@Ignore public class {{classname}}Test { private final {{classname}} api = new {{classname}}(); - {{#operations}}{{#operation}} + {{#operations}} + {{#operation}} /** * {{summary}} * @@ -39,9 +37,12 @@ public class {{classname}}Test { {{#allParams}} {{{dataType}}} {{paramName}} = null; {{/allParams}} - {{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + // uncomment below to test the API function + //{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); // TODO: test validations } - {{/operation}}{{/operations}} + + {{/operation}} + {{/operations}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.gradle.mustache index c14b05a57..3dbb1f667 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -98,10 +98,10 @@ if(hasProperty('target') && target == 'android') { ext { swagger_annotations_version = "1.6.3" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" google_api_client_version = "1.32.2" diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.sbt.mustache index 78da21f2e..a849d22be 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/build.sbt.mustache @@ -3,10 +3,10 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( "io.swagger" % "swagger-annotations" % "1.5.22", diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/pom.mustache index efd1199b2..3c0378942 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/google-api-client/pom.mustache @@ -63,17 +63,16 @@ org.apache.maven.plugins maven-surefire-plugin - 2.12 + 2.22.2 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods - pertest @@ -213,6 +212,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} com.google.code.findbugs @@ -288,27 +294,37 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test UTF-8 + {{#swagger1AnnotationLibrary}} 1.6.6 - 1.32.2 - 2.25.1 - 2.13.4 - 2.13.4.2 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 2.2.0 + 2.40 + 2.19.2 + 2.19.2 {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} {{#joda}} 2.9.9 {{/joda}} + {{#useJakartaEe}} + 2.1.1 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 + {{/useJakartaEe}} 1.0.0 - 4.13.2 + 5.10.2 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/AbstractOpenApiSchema.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/AbstractOpenApiSchema.mustache index 00253ccee..8e5c32da7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/AbstractOpenApiSchema.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/AbstractOpenApiSchema.mustache @@ -6,14 +6,15 @@ import {{invokerPackage}}.ApiException; import java.util.Objects; import java.lang.reflect.Type; import java.util.Map; -import javax.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.GenericType; import com.fasterxml.jackson.annotation.JsonValue; /** * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}} +{{>generatedAnnotation}} + public abstract class AbstractOpenApiSchema { // store the actual instance of the schema/object @@ -35,7 +36,7 @@ public abstract class AbstractOpenApiSchema { * * @return an instance of the actual schema/object */ - public abstract Map getSchemas(); + public abstract Map> getSchemas(); /** * Get the actual instance @@ -135,4 +136,5 @@ public abstract class AbstractOpenApiSchema { {{>libraries/jersey2/additional_properties}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/ApiClient.mustache index 2a027220d..2de6367d9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/ApiClient.mustache @@ -1,15 +1,17 @@ +{{>licenseInfo}} + package {{invokerPackage}}; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Form; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; +import {{javaxPackage}}.ws.rs.client.Client; +import {{javaxPackage}}.ws.rs.client.ClientBuilder; +import {{javaxPackage}}.ws.rs.client.Entity; +import {{javaxPackage}}.ws.rs.client.Invocation; +import {{javaxPackage}}.ws.rs.client.WebTarget; +import {{javaxPackage}}.ws.rs.core.Form; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.MediaType; +import {{javaxPackage}}.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.Response.Status; {{#hasOAuthMethods}} import com.github.scribejava.core.model.OAuth2AccessToken; @@ -38,6 +40,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import org.glassfish.jersey.logging.LoggingFeature; +import java.util.AbstractMap.SimpleEntry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.Collection; @@ -45,11 +48,14 @@ import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Arrays; import java.util.ArrayList; import java.util.Date; +import java.util.stream.Collectors; +import java.util.stream.Stream; {{#jsr310}} import java.time.OffsetDateTime; {{/jsr310}} @@ -78,85 +84,99 @@ import {{invokerPackage}}.auth.OAuth; *

ApiClient class.

*/ {{>generatedAnnotation}} + public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { - protected Map defaultHeaderMap = new HashMap(); - protected Map defaultCookieMap = new HashMap(); + protected static final Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + + protected Map defaultHeaderMap = new HashMap<>(); + protected Map defaultCookieMap = new HashMap<>(); protected String basePath = "{{{basePath}}}"; protected String userAgent; - private static final Logger log = Logger.getLogger(ApiClient.class.getName()); - - 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}} + protected static final Logger log = Logger.getLogger(ApiClient.class.getName()); + + protected List servers = new ArrayList<>({{#servers}}{{#-first}}Arrays.asList( +{{/-first}} new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} {{#-last}} ){{/-last}}{{/servers}}); protected Integer serverIndex = 0; protected Map serverVariables = null; - protected Map> operationServers = new HashMap>() {{ + {{^hasOperationServers}} + protected Map> operationServers = new HashMap<>(); + {{/hasOperationServers}} + {{#hasOperationServers}} + protected Map> operationServers; + + { + Map> operationServers = new HashMap<>(); {{#apiInfo}} {{#apis}} {{#operations}} {{#operation}} {{#servers}} {{#-first}} - put("{{{classname}}}.{{{operationId}}}", new ArrayList(Arrays.asList( + operationServers.put("{{{classname}}}.{{{operationId}}}", new ArrayList<>(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}} + new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} {{#-last}} - )));{{/-last}} + ))); + {{/-last}} {{/servers}} {{/operation}} {{/operations}} {{/apis}} {{/apiInfo}} - }}; - protected Map operationServerIndex = new HashMap(); - protected Map> operationServerVariables = new HashMap>(); + this.operationServers = operationServers; + } + + {{/hasOperationServers}} + protected Map operationServerIndex = new HashMap<>(); + protected Map> operationServerVariables = new HashMap<>(); protected boolean debugging = false; protected ClientConfig clientConfig; protected int connectionTimeout = 0; - private int readTimeout = 0; + protected int readTimeout = 0; protected Client httpClient; protected JSON json; @@ -189,7 +209,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); // Setup authentications (key: authentication name, value: authentication). - authentications = new HashMap(); + authentications = new HashMap<>(); Authentication auth = null; {{#authMethods}} if (authMap != null) { @@ -235,7 +255,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { authentications = Collections.unmodifiableMap(authentications); // Setup authentication lookup (key: authentication alias, value: authentication name) - authenticationLookup = new HashMap();{{#authMethods}}{{#vendorExtensions.x-auth-id-alias}} + authenticationLookup = new HashMap<>();{{#authMethods}}{{#vendorExtensions.x-auth-id-alias}} authenticationLookup.put("{{name}}", "{{.}}");{{/vendorExtensions.x-auth-id-alias}}{{/authMethods}} } @@ -251,7 +271,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Getter for the field httpClient.

* - * @return a {@link javax.ws.rs.client.Client} object. + * @return a {@link {{javaxPackage}}.ws.rs.client.Client} object. */ public Client getHttpClient() { return httpClient; @@ -260,8 +280,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Setter for the field httpClient.

* - * @param httpClient a {@link javax.ws.rs.client.Client} object. - * @return a {@link org.openapitools.client.ApiClient} object. + * @param httpClient a {@link {{javaxPackage}}.ws.rs.client.Client} object. + * @return a {@link ApiClient} object. */ public ApiClient setHttpClient(Client httpClient) { this.httpClient = httpClient; @@ -281,7 +301,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Sets the base URL to the location where the OpenAPI document is being served. * * @param basePath The base URL to the target host. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setBasePath(String basePath) { this.basePath = basePath; @@ -304,7 +324,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field servers.

* * @param servers a {@link java.util.List} of servers. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServers(List servers) { this.servers = servers; @@ -325,7 +345,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field serverIndex.

* * @param serverIndex the server index - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServerIndex(Integer serverIndex) { this.serverIndex = serverIndex; @@ -346,7 +366,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field serverVariables.

* * @param serverVariables a {@link java.util.Map} of server variables. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServerVariables(Map serverVariables) { this.serverVariables = serverVariables; @@ -354,14 +374,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return this; } - private void updateBasePath() { + protected void updateBasePath() { if (serverIndex != null) { setBasePath(servers.get(serverIndex).URL(serverVariables)); } } {{#hasOAuthMethods}} - private void setOauthBasePath(String basePath) { + protected void setOauthBasePath(String basePath) { for(Authentication auth : authentications.values()) { if (auth instanceof OAuth) { ((OAuth) auth).setBasePath(basePath); @@ -393,7 +413,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set username for the first HTTP basic authentication. * * @param username Username - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setUsername(String username) { for (Authentication auth : authentications.values()) { @@ -409,7 +429,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set password for the first HTTP basic authentication. * * @param password Password - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setPassword(String password) { for (Authentication auth : authentications.values()) { @@ -425,7 +445,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set API key value for the first API key authentication. * * @param apiKey API key - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setApiKey(String apiKey) { for (Authentication auth : authentications.values()) { @@ -441,7 +461,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to configure authentications which respects aliases of API keys. * * @param secrets Hash map from authentication name to its secret. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient configureApiKeys(Map secrets) { for (Map.Entry authEntry : authentications.entrySet()) { @@ -449,9 +469,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { if (auth instanceof ApiKeyAuth) { String name = authEntry.getKey(); // respect x-auth-id-alias property - name = authenticationLookup.containsKey(name) ? authenticationLookup.get(name) : name; - if (secrets.containsKey(name)) { - ((ApiKeyAuth) auth).setApiKey(secrets.get(name)); + name = authenticationLookup.getOrDefault(name, name); + String secret = secrets.get(name); + if (secret != null) { + ((ApiKeyAuth) auth).setApiKey(secret); } } } @@ -462,7 +483,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set API key prefix for the first API key authentication. * * @param apiKeyPrefix API key prefix - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setApiKeyPrefix(String apiKeyPrefix) { for (Authentication auth : authentications.values()) { @@ -478,7 +499,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set bearer token for the first Bearer authentication. * * @param bearerToken Bearer token - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setBearerToken(String bearerToken) { for (Authentication auth : authentications.values()) { @@ -495,7 +516,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set access token for the first OAuth2 authentication. * * @param accessToken Access token - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setAccessToken(String accessToken) { for (Authentication auth : authentications.values()) { @@ -512,7 +533,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param clientId the client ID * @param clientSecret the client secret - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthCredentials(String clientId, String clientSecret) { for (Authentication auth : authentications.values()) { @@ -528,7 +549,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the credentials of a public client for the first OAuth2 authentication. * * @param clientId the client ID - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthCredentialsForPublicClient(String clientId) { for (Authentication auth : authentications.values()) { @@ -545,7 +566,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param username the user name * @param password the user password - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthPasswordFlow(String username, String password) { for (Authentication auth : authentications.values()) { @@ -561,7 +582,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the authorization code flow for the first OAuth2 authentication. * * @param code the authorization code - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthAuthorizationCodeFlow(String code) { for (Authentication auth : authentications.values()) { @@ -577,7 +598,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the scopes for the first OAuth2 authentication. * * @param scope the oauth scope - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthScope(String scope) { for (Authentication auth : authentications.values()) { @@ -594,7 +615,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the User-Agent header's value (by adding to the default header map). * * @param userAgent Http user agent - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setUserAgent(String userAgent) { this.userAgent = userAgent; @@ -616,7 +637,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param key The header's key * @param value The header's value - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient addDefaultHeader(String key, String value) { defaultHeaderMap.put(key, value); @@ -628,7 +649,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param key The cookie's key * @param value The cookie's value - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient addDefaultCookie(String key, String value) { defaultCookieMap.put(key, value); @@ -648,7 +669,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the client config. * * @param clientConfig Set the client config - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setClientConfig(ClientConfig clientConfig) { this.clientConfig = clientConfig; @@ -670,10 +691,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Enable/disable debugging for this API client. * * @param debugging To enable (true) or disable (false) debugging - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setDebugging(boolean debugging) { this.debugging = debugging; + applyDebugSetting(this.clientConfig); // Rebuild HTTP Client according to the new "debugging" value. this.httpClient = buildHttpClient(); return this; @@ -694,7 +716,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set temp folder path * * @param tempFolderPath Temp folder path - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setTempFolderPath(String tempFolderPath) { this.tempFolderPath = tempFolderPath; @@ -716,7 +738,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * {@link Integer#MAX_VALUE}. * * @param connectionTimeout Connection timeout in milliseconds - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setConnectTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; @@ -739,7 +761,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * {@link Integer#MAX_VALUE}. * * @param readTimeout Read timeout in milliseconds - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; @@ -760,7 +782,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the date format used to parse/format date parameters. * * @param dateFormat Date format - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; @@ -806,9 +828,9 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return formatDate((Date) param); } {{#jsr310}}else if (param instanceof OffsetDateTime) { return formatOffsetDateTime((OffsetDateTime) param); - } {{/jsr310}}else if (param instanceof Collection) { + } {{/jsr310}}else if (param instanceof Collection) { StringBuilder b = new StringBuilder(); - for(Object o : (Collection)param) { + for(Object o : (Collection)param) { if(b.length() > 0) { b.append(','); } @@ -829,14 +851,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return List of pairs */ public List parameterToPairs(String collectionFormat, String name, Object value){ - List params = new ArrayList(); + List params = new ArrayList<>(); // preconditions if (name == null || name.isEmpty() || value == null) return params; - Collection valueCollection; - if (value instanceof Collection) { - valueCollection = (Collection) value; + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; } else { params.add(new Pair(name, parameterToString(value))); return params; @@ -888,14 +910,13 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * application/json; charset=UTF8 * APPLICATION/JSON * application/vnd.company+json - * "* / *" is also default to JSON + * "*{@literal /}*" is also considered JSON by this method. * * @param mime MIME * @return True if the MIME type is JSON */ public boolean isJsonMime(String mime) { - String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; - return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + return mime != null && (mime.equals("*/*") || JSON_MIME_PATTERN.matcher(mime).matches()); } /** @@ -907,8 +928,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @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) { + public String selectHeaderAccept(String... accepts) { + if (accepts == null || accepts.length == 0) { return null; } for (String accept : accepts) { @@ -928,8 +949,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return The Content-Type header to use. If the given array is empty, * JSON will be used. */ - public String selectHeaderContentType(String[] contentTypes) { - if (contentTypes.length == 0) { + public String selectHeaderContentType(String... contentTypes) { + if (contentTypes == null || contentTypes.length == 0) { return "application/json"; } for (String contentType : contentTypes) { @@ -969,14 +990,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { if (contentType.startsWith("multipart/form-data")) { MultiPart multiPart = new MultiPart(); for (Entry param: formParams.entrySet()) { - if (param.getValue() instanceof File) { - File file = (File) param.getValue(); - FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()) - .fileName(file.getName()).size(file.length()).build(); - multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + if (param.getValue() instanceof Iterable) { + ((Iterable)param.getValue()).forEach(v -> addParamToMultipart(v, param.getKey(), multiPart)); } else { - FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()).build(); - multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(param.getValue()))); + addParamToMultipart(param.getValue(), param.getKey(), multiPart); } } entity = Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE); @@ -1005,6 +1022,36 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return entity; } + /** + * Adds the object with the provided key to the MultiPart. + * Based on the object type sets Content-Disposition and Content-Type. + * + * @param obj Object + * @param key Key of the object + * @param multiPart MultiPart to add the form param to + */ + protected void addParamToMultipart(Object value, String key, MultiPart multiPart) { + if (value instanceof File) { + File file = (File) value; + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key) + .fileName(file.getName()).size(file.length()).build(); + + // Attempt to probe the content type for the file so that the form part is more correctly + // and precisely identified, but fall back to application/octet-stream if that fails. + MediaType type; + try { + type = MediaType.valueOf(Files.probeContentType(file.toPath())); + } catch (IOException | IllegalArgumentException e) { + type = MediaType.APPLICATION_OCTET_STREAM_TYPE; + } + + multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, type)); + } else { + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build(); + multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value))); + } + } + /** * Serialize the given Java object into string according the given * Content-Type (only JSON, HTTP form is supported for now). @@ -1067,11 +1114,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return file; } - String contentType = null; - List contentTypes = response.getHeaders().get("Content-Type"); - if (contentTypes != null && !contentTypes.isEmpty()) - contentType = String.valueOf(contentTypes.get(0)); - // read the entity stream multiple times response.bufferEntity(); @@ -1098,7 +1140,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Prepare the file for download from the response.

* - * @param response a {@link javax.ws.rs.core.Response} object. + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. * @return a {@link java.io.File} object. * @throws java.io.IOException if any. */ @@ -1173,17 +1215,15 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { boolean isBodyNullable) throws ApiException { - // Not using `.target(targetURL).path(path)` below, - // to support (constant) query string in `path`, e.g. "/posts?draft=1" String targetURL; - if (serverIndex != null && operationServers.containsKey(operation)) { - Integer index = operationServerIndex.containsKey(operation) ? operationServerIndex.get(operation) : serverIndex; - Map variables = operationServerVariables.containsKey(operation) ? - operationServerVariables.get(operation) : serverVariables; - List serverConfigurations = operationServers.get(operation); + List serverConfigurations; + if (serverIndex != null && (serverConfigurations = operationServers.get(operation)) != null) { + int index = operationServerIndex.getOrDefault(operation, serverIndex).intValue(); + Map variables = operationServerVariables.getOrDefault(operation, serverVariables); if (index < 0 || index >= serverConfigurations.size()) { throw new ArrayIndexOutOfBoundsException( String.format( + java.util.Locale.ROOT, "Invalid index %d when selecting the host settings. Must be less than %d", index, serverConfigurations.size())); } @@ -1191,8 +1231,31 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } else { targetURL = this.basePath + path; } + // Not using `.target(targetURL).path(path)` below, + // to support (constant) query string in `path`, e.g. "/posts?draft=1" WebTarget target = httpClient.target(targetURL); + // put all headers in one place + Map allHeaderParams = new HashMap<>(defaultHeaderMap); + allHeaderParams.putAll(headerParams); + + if (authNames != null) { + // update different parameters (e.g. headers) for authentication + updateParamsForAuth( + authNames, + queryParams, + allHeaderParams, + cookieParams, + {{#hasHttpSignatureMethods}} + serializeToString(body, formParams, contentType, isBodyNullable), + {{/hasHttpSignatureMethods}} + {{^hasHttpSignatureMethods}} + null, + {{/hasHttpSignatureMethods}} + method, + target.getUri()); + } + if (queryParams != null) { for (Pair queryParam : queryParams) { if (queryParam.getValue() != null) { @@ -1201,11 +1264,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - Invocation.Builder invocationBuilder; + Invocation.Builder invocationBuilder = target.request(); + if (accept != null) { - invocationBuilder = target.request().accept(accept); - } else { - invocationBuilder = target.request(); + invocationBuilder = invocationBuilder.accept(accept); } for (Entry entry : cookieParams.entrySet()) { @@ -1224,25 +1286,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { Entity entity = serialize(body, formParams, contentType, isBodyNullable); - // put all headers in one place - Map allHeaderParams = new HashMap<>(defaultHeaderMap); - allHeaderParams.putAll(headerParams); - - // update different parameters (e.g. headers) for authentication - updateParamsForAuth( - authNames, - queryParams, - allHeaderParams, - cookieParams, - {{#hasHttpSignatureMethods}} - serializeToString(body, formParams, contentType, isBodyNullable), - {{/hasHttpSignatureMethods}} - {{^hasHttpSignatureMethods}} - null, - {{/hasHttpSignatureMethods}} - method, - target.getUri()); - for (Entry entry : allHeaderParams.entrySet()) { String value = entry.getValue(); if (value != null) { @@ -1257,7 +1300,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { {{#hasOAuthMethods}} // If OAuth is used and a status 401 is received, renew the access token and retry the request - if (response.getStatusInfo() == Status.UNAUTHORIZED) { + if (authNames != null && response.getStatusInfo().getStatusCode() == Status.UNAUTHORIZED.getStatusCode()) { for (String authName : authNames) { Authentication authentication = authentications.get(authName); if (authentication instanceof OAuth) { @@ -1271,12 +1314,13 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } } - + {{/hasOAuthMethods}} - int statusCode = response.getStatusInfo().getStatusCode(); + final int statusCode = response.getStatusInfo().getStatusCode(); + Map> responseHeaders = buildResponseHeaders(response); - if (response.getStatusInfo() == Status.NO_CONTENT) { + if (statusCode == Status.NO_CONTENT.getStatusCode()) { return new ApiResponse(statusCode, responseHeaders); } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { if (returnType == null) { @@ -1308,14 +1352,18 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - private Response sendRequest(String method, Invocation.Builder invocationBuilder, Entity entity) { + protected Response sendRequest(String method, Invocation.Builder invocationBuilder, Entity entity) { Response response; if ("POST".equals(method)) { response = invocationBuilder.post(entity); } else if ("PUT".equals(method)) { response = invocationBuilder.put(entity); } else if ("DELETE".equals(method)) { - response = invocationBuilder.method("DELETE", entity); + if ("".equals(entity.getEntity())) { + response = invocationBuilder.method("DELETE"); + } else { + response = invocationBuilder.method("DELETE", entity); + } } else if ("PATCH".equals(method)) { response = invocationBuilder.method("PATCH", entity); } else { @@ -1338,12 +1386,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return Client */ protected Client buildHttpClient() { - // recreate the client config to pickup changes - clientConfig = getDefaultClientConfig(); + // Create ClientConfig if it has not been initialized yet + if (clientConfig == null) { + clientConfig = getDefaultClientConfig(); + } ClientBuilder clientBuilder = ClientBuilder.newBuilder(); - customizeClientBuilder(clientBuilder); clientBuilder = clientBuilder.withConfig(clientConfig); + customizeClientBuilder(clientBuilder); return clientBuilder.build(); } @@ -1360,6 +1410,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { clientConfig.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // turn off compliance validation to be able to send payloads with DELETE calls clientConfig.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); + applyDebugSetting(clientConfig); + return clientConfig; + } + + protected void applyDebugSetting(ClientConfig clientConfig) { if (debugging) { clientConfig.register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), java.util.logging.Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024*50 /* Log payloads up to 50K */)); clientConfig.property(LoggingFeature.LOGGING_FEATURE_VERBOSITY, LoggingFeature.Verbosity.PAYLOAD_ANY); @@ -1369,8 +1424,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // suppress warnings for payloads with DELETE calls: java.util.logging.Logger.getLogger("org.glassfish.jersey.client").setLevel(java.util.logging.Level.SEVERE); } - - return clientConfig; } /** @@ -1387,7 +1440,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * To completely disable certificate validation (at your own risk), you can * override this method and invoke disableCertificateValidation(clientBuilder). * - * @param clientBuilder a {@link javax.ws.rs.client.ClientBuilder} object. + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. */ protected void customizeClientBuilder(ClientBuilder clientBuilder) { // No-op extension point @@ -1399,7 +1452,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Please note that trusting all certificates is extremely risky. * This may be useful in a development environment with self-signed certificates. * - * @param clientBuilder a {@link javax.ws.rs.client.ClientBuilder} object. + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. * @throws java.security.KeyManagementException if any. * @throws java.security.NoSuchAlgorithmException if any. */ @@ -1426,14 +1479,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Build the response headers.

* - * @param response a {@link javax.ws.rs.core.Response} object. + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. * @return a {@link java.util.Map} of response headers. */ protected Map> buildResponseHeaders(Response response) { - Map> responseHeaders = new HashMap>(); + Map> responseHeaders = new HashMap<>(); for (Entry> entry: response.getHeaders().entrySet()) { List values = entry.getValue(); - List headers = new ArrayList(); + List headers = new ArrayList<>(); for (Object o : values) { headers.add(String.valueOf(o)); } @@ -1462,4 +1515,4 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { auth.applyToParams(queryParams, headerParams, cookieParams, payload, method, uri); } } -} +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/JSON.mustache index 8de585519..55b6e232c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/JSON.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.annotation.*; @@ -10,40 +12,39 @@ 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; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.ext.ContextResolver; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.ext.ContextResolver; {{>generatedAnnotation}} + public class JSON implements ContextResolver { private ObjectMapper mapper; public JSON() { - mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - JsonMapper.builder().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}} + mapper = JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .defaultDateFormat(new RFC3339DateFormat()) + .addModule(new JavaTimeModule()) + {{#joda}} + .addModule(new JodaModule()) + {{/joda}} + {{#openApiNullable}} + .addModule(new JsonNullableModule()) + {{/openApiNullable}} + .addModule(new RFC3339JavaTimeModule()) + .build(); } /** @@ -76,7 +77,7 @@ public class JSON implements ContextResolver { public static Class getClassForElement(JsonNode node, Class modelClass) { ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass); if (cdm != null) { - return cdm.getClassForElement(node, new HashSet>()); + return cdm.getClassForElement(node, new HashSet<>()); } return null; } @@ -96,7 +97,7 @@ public class JSON implements ContextResolver { ClassDiscriminatorMapping(Class cls, String propertyName, Map> mappings) { modelClass = cls; discriminatorName = propertyName; - discriminatorMappings = new HashMap>(); + discriminatorMappings = new HashMap<>(); if (mappings != null) { discriminatorMappings.putAll(mappings); } @@ -191,9 +192,9 @@ public class JSON implements ContextResolver { visitedClasses.add(modelClass); // Traverse the oneOf/anyOf composed schemas. - Map descendants = modelDescendants.get(modelClass); + Map> descendants = modelDescendants.get(modelClass); if (descendants != null) { - for (GenericType childType : descendants.values()) { + for (GenericType childType : descendants.values()) { if (isInstanceOf(childType.getRawType(), inst, visitedClasses)) { return true; } @@ -205,12 +206,12 @@ public class JSON implements ContextResolver { /** * A map of discriminators for all model classes. */ - private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap, ClassDiscriminatorMapping>(); + private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>(); /** * A map of oneOf/anyOf descendants for each model class. */ - private static Map, Map> modelDescendants = new HashMap, Map>(); + private static Map, Map>> modelDescendants = new HashMap<>(); /** * Register a model class discriminator. @@ -230,7 +231,7 @@ public class JSON implements ContextResolver { * @param modelClass the model class * @param descendants a map of oneOf/anyOf descendants. */ - public static void registerDescendants(Class modelClass, Map descendants) { + public static void registerDescendants(Class modelClass, Map> descendants) { modelDescendants.put(modelClass, descendants); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/additional_properties.mustache index 61973dc24..2955e9392 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/additional_properties.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/additional_properties.mustache @@ -13,7 +13,7 @@ @JsonAnySetter public {{classname}} putAdditionalProperty(String key, {{{.}}} value) { if (this.additionalProperties == null) { - this.additionalProperties = new HashMap(); + this.additionalProperties = new HashMap<>(); } this.additionalProperties.put(key, value); return this; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/anyof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/anyof_model.mustache index d5b381987..ddd314493 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/anyof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/anyof_model.mustache @@ -1,5 +1,5 @@ -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.Response; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -24,7 +24,7 @@ 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -57,7 +57,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im Object deserialized = null; {{#discriminator}} - Class cls = JSON.getClassForElement(tree, {{classname}}.class); + Class cls = JSON.getClassForElement(tree, new {{classname}}().getClass()); if (cls != null) { // When the OAS schema includes a discriminator, use the discriminator value to // discriminate the anyOf schemas. @@ -68,10 +68,23 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return ret; } {{/discriminator}} + {{#composedSchemas}} {{#anyOf}} - // deserialize {{{.}}} + // deserialize {{{dataType}}}{{#isNullable}} (nullable){{/isNullable}} try { - deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + {{^isArray}} + {{^isMap}} + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{dataType}}}.class); + {{/isMap}} + {{/isArray}} + {{#isArray}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isArray}} + {{#isMap}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isMap}} {{classname}} ret = new {{classname}}(); ret.setActualInstance(deserialized); return ret; @@ -81,7 +94,8 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/anyOf}} - throw new IOException(String.format("Failed deserialization for {{classname}}: no match found")); + {{/composedSchemas}} + throw new IOException("Failed deserialization for {{classname}}: no match found"); } /** @@ -99,12 +113,13 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } // store a list of schema names defined in anyOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap<>(); public {{classname}}() { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } {{> libraries/jersey2/additional_properties }} + {{#additionalPropertiesType}} /** * Return true if this {{name}} object is equal to o. @@ -119,13 +134,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); } {{/additionalPropertiesType}} + {{#composedSchemas}} {{#anyOf}} - public {{classname}}({{{.}}} o) { + {{^vendorExtensions.x-duplicated-data-type}} + public {{classname}}({{{baseType}}} o) { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} static { {{#anyOf}} schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { @@ -134,7 +153,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); {{#discriminator}} // Initialize and register the discriminator mappings. - Map> mappings = new HashMap>(); + Map> mappings = new HashMap<>(); {{#mappedModels}} mappings.put("{{mappingName}}", {{modelName}}.class); {{/mappedModels}} @@ -144,7 +163,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -165,13 +184,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#anyOf}} - if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + {{^vendorExtensions.x-duplicated-data-type}} + if (JSON.isInstanceOf({{{baseType}}}.class, instance, new HashSet<>())) { super.setActualInstance(instance); return; } + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}"); } @@ -186,17 +209,21 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return super.getActualInstance(); } + {{#composedSchemas}} {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/anyOf}} + {{/composedSchemas}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api.mustache index 756f0c129..c663d7580 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api.mustache @@ -6,19 +6,24 @@ import {{invokerPackage}}.ApiResponse; import {{invokerPackage}}.Configuration; import {{invokerPackage}}.Pair; -import javax.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.GenericType; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { private ApiClient apiClient; @@ -63,7 +68,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details - +
+ {{#responses}} @@ -81,7 +87,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { {{#returnType}}return {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}}.getData(){{/returnType}}; } {{/vendorExtensions.x-group-parameters}} @@ -97,7 +103,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -115,68 +122,95 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#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 + public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#hasRequiredParams}} + // Check required parameters + {{#allParams}} + {{#required}} 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}}; + {{/required}} + {{/allParams}} - // query params - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + {{/hasRequiredParams}} + {{#hasPathParams}} + // Path parameters + String localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll({{=% %=}}"\\{%baseName%}"%={{ }}=%, apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + {{/hasPathParams}} {{#queryParams}} + {{#-first}} + // Query parameters + List localVarQueryParams = new ArrayList<>( + apiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}}) + ); + {{/-first}} + {{^-first}} localVarQueryParams.addAll(apiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}})); + {{/-first}} + {{#-last}} + + {{/-last}} {{/queryParams}} + {{#headerParams}} + {{#-first}} + // Header parameters + Map localVarHeaderParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^required}} + }{{/required}} + {{#-last}} - {{#headerParams}}if ({{paramName}} != null) - localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/-last}} {{/headerParams}} + {{#cookieParams}} + {{#-first}} + // Cookie parameters + Map localVarCookieParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^required}} + }{{/required}} + {{#-last}} - {{#cookieParams}}if ({{paramName}} != null) - localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/-last}} {{/cookieParams}} + {{#formParams}} + {{#-first}} + // Form parameters + Map localVarFormParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarFormParams.put("{{baseName}}", {{paramName}});{{^required}} + }{{/required}} + {{#-last}} - {{#formParams}}if ({{paramName}} != null) - localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/-last}} {{/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}} }; - + String localVarAccept = apiClient.selectHeaderAccept({{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}); + String localVarContentType = apiClient.selectHeaderContentType({{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}); + {{#hasAuthMethods}} + String[] localVarAuthNames = {{=% %=}}new String[] {%#authMethods%"%name%"%^-last%, %/-last%%/authMethods%};%={{ }}=% + {{/hasAuthMethods}} {{#returnType}} GenericType<{{{returnType}}}> localVarReturnType = new GenericType<{{{returnType}}}>() {}; - {{/returnType}} - return apiClient.invokeAPI("{{classname}}.{{operationId}}", localVarPath, "{{httpMethod}}", localVarQueryParams, localVarPostBody, - localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, - localVarAuthNames, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}}, {{#bodyParam}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/bodyParam}}{{^bodyParam}}false{{/bodyParam}}); + return apiClient.invokeAPI("{{classname}}.{{operationId}}", {{#hasPathParams}}localVarPath{{/hasPathParams}}{{^hasPathParams}}"{{{path}}}"{{/hasPathParams}}, "{{httpMethod}}", {{#queryParams}}{{#-first}}localVarQueryParams{{/-first}}{{/queryParams}}{{^queryParams}}new ArrayList<>(){{/queryParams}}, {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, + {{#headerParams}}{{#-first}}localVarHeaderParams{{/-first}}{{/headerParams}}{{^headerParams}}new LinkedHashMap<>(){{/headerParams}}, {{#cookieParams}}{{#-first}}localVarCookieParams{{/-first}}{{/cookieParams}}{{^cookieParams}}new LinkedHashMap<>(){{/cookieParams}}, {{#formParams}}{{#-first}}localVarFormParams{{/-first}}{{/formParams}}{{^formParams}}new LinkedHashMap<>(){{/formParams}}, localVarAccept, localVarContentType, + {{#hasAuthMethods}}localVarAuthNames{{/hasAuthMethods}}{{^hasAuthMethods}}null{{/hasAuthMethods}}, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}}, {{#bodyParam}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/bodyParam}}{{^bodyParam}}false{{/bodyParam}}); } {{#vendorExtensions.x-group-parameters}} public class API{{operationId}}Request { {{#allParams}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{dataType}}} {{paramName}}; {{/allParams}} - private API{{operationId}}Request({{#pathParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) { + private API{{operationId}}Request({{#pathParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) { {{#pathParams}} this.{{paramName}} = {{paramName}}; {{/pathParams}} @@ -189,7 +223,7 @@ public class {{classname}} { * @param {{paramName}} {{description}} ({{^required}}optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}{{/required}}{{#required}}required{{/required}}) * @return API{{operationId}}Request */ - public API{{operationId}}Request {{paramName}}({{{dataType}}} {{paramName}}) { + public API{{operationId}}Request {{paramName}}({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { this.{{paramName}} = {{paramName}}; return this; } @@ -202,7 +236,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -222,7 +257,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -253,7 +289,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public API{{operationId}}Request {{operationId}}({{#pathParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) throws ApiException { + public API{{operationId}}Request {{operationId}}({{#pathParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) throws ApiException { return new API{{operationId}}Request({{#pathParams}}{{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}); } {{/vendorExtensions.x-group-parameters}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/apiException.mustache index 74a419aac..d957acd81 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/apiException.mustache @@ -13,7 +13,10 @@ import java.util.TreeMap; * API Exception */ {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private Map> responseHeaders = null; private String responseBody = null; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api_test.mustache index eb5101b1a..926ba0a82 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/api_test.mustache @@ -11,13 +11,16 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/ApiKeyAuth.mustache index 1731f936f..82576065f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/ApiKeyAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/Authentication.mustache index 46d9c1ab6..169e3abdc 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/Authentication.mustache @@ -9,6 +9,8 @@ import java.net.URI; import java.util.Map; import java.util.List; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and query params. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBasicAuth.mustache index 13cecfa90..2a8774b6a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBasicAuth.mustache @@ -13,6 +13,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBearerAuth.mustache index 110a11ea7..cb78e4799 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/HttpBearerAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; private String bearerToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/OAuth.mustache index 8908aa543..4725badff 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/auth/OAuth.mustache @@ -10,7 +10,7 @@ import com.github.scribejava.core.exceptions.OAuthException; import com.github.scribejava.core.model.OAuth2AccessToken; import com.github.scribejava.core.oauth.OAuth20Service; -import javax.ws.rs.core.UriBuilder; +import {{javaxPackage}}.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -21,6 +21,7 @@ import java.util.logging.Level; import java.util.logging.Logger; {{>generatedAnnotation}} + public class OAuth implements Authentication { private static final Logger log = Logger.getLogger(OAuth.class.getName()); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.gradle.mustache index b11d0add3..52ebafbf1 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.gradle.mustache @@ -58,9 +58,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -98,13 +98,21 @@ if(hasProperty('target') && target == 'android') { } ext { - swagger_annotations_version = "1.6.5" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + {{#swagger1AnnotationLibrary}} + swagger_annotations_version = "1.6.6" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.15" + {{/swagger2AnnotationLibrary}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" + {{#useBeanValidation}} + bean_validation_version = "3.0.2" + {{/useBeanValidation}} jersey_version = "2.35" junit_version = "5.8.2" {{#hasOAuthMethods}} @@ -113,10 +121,18 @@ ext { {{#hasHttpSignatureMethods}} tomitribe_http_signatures_version = "1.7" {{/hasHttpSignatureMethods}} + {{#useReflectionEqualsHashCode}} + commons_lang3_version = "3.17.0" + {{/useReflectionEqualsHashCode}} } 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 "org.glassfish.jersey.core:jersey-client:$jersey_version" implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version" @@ -140,6 +156,12 @@ dependencies { implementation "org.tomitribe:tomitribe-http-signatures:$tomitribe_http_signatures_version" {{/hasHttpSignatureMethods}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$bean_validation_version" + {{/useBeanValidation}} + {{#useReflectionEqualsHashCode}} + implementation "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/useReflectionEqualsHashCode}} testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.sbt.mustache index dc65e1b49..a523671d9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/build.sbt.mustache @@ -3,7 +3,7 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), Compile / javacOptions ++= Seq("-Xlint:deprecation"), Compile / packageDoc / publishArtifact := false, @@ -16,15 +16,15 @@ lazy val root = (project in file(".")). "org.glassfish.jersey.media" % "jersey-media-multipart" % "2.35", "org.glassfish.jersey.media" % "jersey-media-json-jackson" % "2.35", "org.glassfish.jersey.connectors" % "jersey-apache-connector" % "2.35", - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.2" % "compile", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.2" % "compile", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.19.2" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.19.2" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.19.2" % "compile", {{#joda}} - "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.13.2" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.19.2" % "compile", {{/joda}} - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.13.2" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.19.2" % "compile", {{#openApiNullable}} - "org.openapitools" % "jackson-databind-nullable" % "0.2.4" % "compile", + "org.openapitools" % "jackson-databind-nullable" % "0.2.9" % "compile", {{/openApiNullable}} {{#hasOAuthMethods}} "com.github.scribejava" % "scribejava-apis" % "8.3.1" % "compile", @@ -33,6 +33,9 @@ lazy val root = (project in file(".")). "org.tomitribe" % "tomitribe-http-signatures" % "1.7" % "compile", {{/hasHttpSignatureMethods}} "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", + {{#useReflectionEqualsHashCode}} + "org.apache.commons" % "commons-lang3" % "3.17.0" % "compile", + {{/useReflectionEqualsHashCode}} "org.junit.jupiter" % "junit-jupiter-api" % "5.8.2" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model.mustache index 914e1eb1e..2758278ac 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model.mustache @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; {{/model}} {{/models}} import java.util.Objects; -import java.util.Arrays; import java.util.Map; import java.util.HashMap; {{#imports}} @@ -36,15 +35,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; {{/vendorExtensions.x-has-readonly-properties}} {{/jackson}} {{#withXml}} -import javax.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.*; {{/withXml}} {{#parcelableModel}} import android.os.Parcelable; import android.os.Parcel; {{/parcelableModel}} {{#useBeanValidation}} -import javax.validation.constraints.*; -import javax.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} {{#performBeanValidation}} import org.hibernate.validator.constraints.*; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_doc.mustache index be1aedcf2..f17263f08 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_doc.mustache @@ -2,17 +2,21 @@ {{#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}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_test.mustache index 2d4ccdd1a..acd659b66 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/model_test.mustache @@ -9,13 +9,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{#fullJavaUtil}} -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -{{/fullJavaUtil}} /** * Model tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/oneof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/oneof_model.mustache index 18bcbc5e1..87af00c8d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/oneof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/oneof_model.mustache @@ -1,5 +1,5 @@ -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.Response; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,7 +26,7 @@ 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -60,7 +60,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{#useOneOfDiscriminatorLookup}} {{#discriminator}} {{classname}} new{{classname}} = new {{classname}}(); - Map result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); + Map result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); String discriminatorValue = (String)result2.get("{{{propertyBaseName}}}"); switch (discriminatorValue) { {{#mappedModels}} @@ -70,7 +70,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im 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)); + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); } {{/discriminator}} @@ -78,43 +78,86 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); int match = 0; JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + {{#composedSchemas}} {{#oneOf}} - // deserialize {{{.}}} + // deserialize {{{dataType}}}{{#isNullable}} (nullable){{/isNullable}} try { + {{^isArray}} 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}} - } + {{#isPrimitiveType}} + attemptParsing = typeCoercion; //respect type coercion setting + if (!attemptParsing) { + {{#isString}} + attemptParsing |= (token == JsonToken.VALUE_STRING); + {{/isString}} + {{#isInteger}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isInteger}} + {{#isLong}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isLong}} + {{#isShort}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isShort}} + {{#isFloat}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isFloat}} + {{#isDouble}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isDouble}} + {{#isNumber}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isNumber}} + {{#isDecimal}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isDecimal}} + {{#isBoolean}} + attemptParsing |= (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE); + {{/isBoolean}} + {{#isNullable}} + attemptParsing |= (token == JsonToken.VALUE_NULL); + {{/isNullable}} } + {{/isPrimitiveType}} if (attemptParsing) { - deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + {{#isMap}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isMap}} + {{^isMap}} + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{dataType}}}.class); + {{/isMap}} // 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 '{{{.}}}'"); + log.log(Level.FINER, "Input data matches schema '{{{dataType}}}'"); } + {{/isArray}} + {{#isArray}} + if (token == JsonToken.START_ARRAY) { + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + // 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 '{{{dataType}}}'"); + } + {{/isArray}} } catch (Exception e) { // deserialization failed, continue - log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); + log.log(Level.FINER, "Input data does not match schema '{{{dataType}}}'", e); } {{/oneOf}} + {{/composedSchemas}} 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)); + throw new IOException(String.format(java.util.Locale.ROOT, "Failed deserialization for {{classname}}: %d classes match result, expected 1", match)); } /** @@ -132,12 +175,13 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } // store a list of schema names defined in oneOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap<>(); public {{classname}}() { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } {{> libraries/jersey2/additional_properties }} + {{#additionalPropertiesType}} /** * Return true if this {{name}} object is equal to o. @@ -152,13 +196,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); } {{/additionalPropertiesType}} + {{#composedSchemas}} {{#oneOf}} - public {{classname}}({{{.}}} o) { + {{^vendorExtensions.x-duplicated-data-type}} + public {{classname}}({{{baseType}}} o) { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} static { {{#oneOf}} schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { @@ -167,7 +215,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); {{#discriminator}} // Initialize and register the discriminator mappings. - Map> mappings = new HashMap>(); + Map> mappings = new HashMap<>(); {{#mappedModels}} mappings.put("{{mappingName}}", {{modelName}}.class); {{/mappedModels}} @@ -177,7 +225,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -198,13 +246,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#oneOf}} - if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + {{^vendorExtensions.x-duplicated-data-type}} + if (JSON.isInstanceOf({{{baseType}}}.class, instance, new HashSet<>())) { super.setActualInstance(instance); return; } + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); } @@ -219,17 +271,21 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return super.getActualInstance(); } + {{#composedSchemas}} {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/oneOf}} + {{/composedSchemas}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pojo.mustache index 4eb9cc2f1..2f4bac6a7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pojo.mustache @@ -8,6 +8,11 @@ @ApiModel(description = "{{{.}}}") {{/description}} {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} {{#jackson}} @JsonPropertyOrder({ {{#vars}} @@ -32,11 +37,13 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{^isContainer}} {{^vendorExtensions.x-enum-as-string}} {{>modelInnerEnum}} + {{/vendorExtensions.x-enum-as-string}} {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} {{>modelInnerEnum}} + {{/mostInnerItems}} {{/isContainer}} {{/isEnum}} @@ -47,47 +54,37 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{/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}} + {{#deprecated}} + @Deprecated + {{/deprecated}} private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); {{/isContainer}} {{^isContainer}} + {{#deprecated}} + @Deprecated + {{/deprecated}} 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}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}}; - {{/isContainer}} - {{^isContainer}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/vars}} @@ -116,7 +113,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens )); {{/vendorExtensions.x-enum-as-string}} - public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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)); @@ -133,10 +133,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { {{#vendorExtensions.x-is-jackson-optional-nullable}} if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); } try { this.{{name}}.get().add({{name}}Item); @@ -146,11 +146,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.add({{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -158,10 +156,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isArray}} {{#isMap}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} put{{nameInPascalCase}}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}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); } try { this.{{name}}.get().put(key, {{name}}Item); @@ -171,11 +169,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.put(key, {{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -183,7 +179,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isMap}} {{/isReadOnly}} - /** + /** {{#description}} * {{.}} {{/description}} @@ -200,27 +196,21 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#deprecated}} * @deprecated {{/deprecated}} - **/ + */ {{#deprecated}} @Deprecated {{/deprecated}} -{{#required}} -{{#isNullable}} - @javax.annotation.Nullable -{{/isNullable}} -{{^isNullable}} - @javax.annotation.Nonnull -{{/isNullable}} -{{/required}} -{{^required}} - @javax.annotation.Nullable -{{/required}} + {{>nullable_var_annotations}}{{! prevent indent}} {{#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}} @@ -245,6 +235,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#vendorExtensions.x-is-jackson-optional-nullable}} {{> jackson_annotations}} + public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { return {{name}}; } @@ -257,8 +248,11 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^isReadOnly}} + {{#deprecated}} + @Deprecated + {{/deprecated}} {{#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-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullable_var_annotations}} {{{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)); @@ -276,6 +270,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/vars}} {{>libraries/jersey2/additional_properties}} + /** * Return true if this {{name}} object is equal to o. */ @@ -329,7 +324,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens sb.append(" ").append(toIndentedString(super.toString())).append("\n"); {{/parent}} {{#vars}} - sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); {{/vars}} {{#additionalPropertiesType}} sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); @@ -390,7 +385,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return 0; } - public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<>() { public {{classname}} createFromParcel(Parcel in) { {{#model}} {{#isArray}} @@ -409,14 +404,14 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens }; {{/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); -} + 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/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pom.mustache index 4bfecc2c6..662e2d741 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey2/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 3.0.0-M5 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods 10 @@ -265,6 +265,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -372,6 +379,15 @@ jersey-apache-connector ${jersey-version} + {{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + {{/useReflectionEqualsHashCode}} + org.junit.jupiter @@ -382,22 +398,34 @@ UTF-8 + {{#swagger1AnnotationLibrary}} 1.6.6 - 2.35 - 2.13.4 - 2.13.4.2 - 0.2.4 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 2.37 + 2.19.2 + 2.19.2 + 0.2.9 + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 - {{#useBeanValidation}} 2.0.2 - {{/useBeanValidation}} - 5.8.2 + {{/useJakartaEe}} + 5.10.0 {{#hasHttpSignatureMethods}} - 1.7 + 1.8 {{/hasHttpSignatureMethods}} {{#hasOAuthMethods}} - 8.3.1 + 8.3.3 {{/hasOAuthMethods}} + {{#useReflectionEqualsHashCode}} + 3.17.0 + {{/useReflectionEqualsHashCode}} 2.21.0 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/AbstractOpenApiSchema.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/AbstractOpenApiSchema.mustache index afbf6a9a0..8e5c32da7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/AbstractOpenApiSchema.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/AbstractOpenApiSchema.mustache @@ -6,14 +6,15 @@ import {{invokerPackage}}.ApiException; import java.util.Objects; import java.lang.reflect.Type; import java.util.Map; -import jakarta.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.GenericType; import com.fasterxml.jackson.annotation.JsonValue; /** * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}} +{{>generatedAnnotation}} + public abstract class AbstractOpenApiSchema { // store the actual instance of the schema/object @@ -35,7 +36,7 @@ public abstract class AbstractOpenApiSchema { * * @return an instance of the actual schema/object */ - public abstract Map getSchemas(); + public abstract Map> getSchemas(); /** * Get the actual instance @@ -135,4 +136,5 @@ public abstract class AbstractOpenApiSchema { {{>libraries/jersey2/additional_properties}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/ApiClient.mustache index a89642a27..3f999481d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/ApiClient.mustache @@ -1,15 +1,17 @@ +{{>licenseInfo}} + package {{invokerPackage}}; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.Invocation; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Form; -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; +import {{javaxPackage}}.ws.rs.client.Client; +import {{javaxPackage}}.ws.rs.client.ClientBuilder; +import {{javaxPackage}}.ws.rs.client.Entity; +import {{javaxPackage}}.ws.rs.client.Invocation; +import {{javaxPackage}}.ws.rs.client.WebTarget; +import {{javaxPackage}}.ws.rs.core.Form; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.MediaType; +import {{javaxPackage}}.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.Response.Status; {{#hasOAuthMethods}} import com.github.scribejava.core.model.OAuth2AccessToken; @@ -38,6 +40,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import org.glassfish.jersey.logging.LoggingFeature; +import java.util.AbstractMap.SimpleEntry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.Collection; @@ -45,11 +48,14 @@ import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Arrays; import java.util.ArrayList; import java.util.Date; +import java.util.stream.Collectors; +import java.util.stream.Stream; {{#jsr310}} import java.time.OffsetDateTime; {{/jsr310}} @@ -78,85 +84,99 @@ import {{invokerPackage}}.auth.OAuth; *

ApiClient class.

*/ {{>generatedAnnotation}} + public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { - protected Map defaultHeaderMap = new HashMap(); - protected Map defaultCookieMap = new HashMap(); + protected static final Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + + protected Map defaultHeaderMap = new HashMap<>(); + protected Map defaultCookieMap = new HashMap<>(); protected String basePath = "{{{basePath}}}"; protected String userAgent; - private static final Logger log = Logger.getLogger(ApiClient.class.getName()); - - 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}} + protected static final Logger log = Logger.getLogger(ApiClient.class.getName()); + + protected List servers = new ArrayList<>({{#servers}}{{#-first}}Arrays.asList( +{{/-first}} new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} {{#-last}} ){{/-last}}{{/servers}}); protected Integer serverIndex = 0; protected Map serverVariables = null; - protected Map> operationServers = new HashMap>() {{ + {{^hasOperationServers}} + protected Map> operationServers = new HashMap<>(); + {{/hasOperationServers}} + {{#hasOperationServers}} + protected Map> operationServers; + + { + Map> operationServers = new HashMap<>(); {{#apiInfo}} {{#apis}} {{#operations}} {{#operation}} {{#servers}} {{#-first}} - put("{{{classname}}}.{{{operationId}}}", new ArrayList(Arrays.asList( + operationServers.put("{{{classname}}}.{{{operationId}}}", new ArrayList<>(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}} + new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + {{^variables}} + new LinkedHashMap<>() + {{/variables}} + {{#variables}} + {{#-first}} + Stream.>of( + {{/-first}} + new SimpleEntry<>("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new LinkedHashSet<>({{#enumValues}}{{#-first}}Arrays.asList({{/-first}} + "{{{.}}}"{{^-last}},{{/-last}}{{#-last}} + ){{/-last}}{{/enumValues}}) + )){{^-last}},{{/-last}} + {{#-last}} + ).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)) + {{/-last}} + {{/variables}} + ){{^-last}},{{/-last}} {{#-last}} - )));{{/-last}} + ))); + {{/-last}} {{/servers}} {{/operation}} {{/operations}} {{/apis}} {{/apiInfo}} - }}; - protected Map operationServerIndex = new HashMap(); - protected Map> operationServerVariables = new HashMap>(); + this.operationServers = operationServers; + } + + {{/hasOperationServers}} + protected Map operationServerIndex = new HashMap<>(); + protected Map> operationServerVariables = new HashMap<>(); protected boolean debugging = false; protected ClientConfig clientConfig; protected int connectionTimeout = 0; - private int readTimeout = 0; + protected int readTimeout = 0; protected Client httpClient; protected JSON json; @@ -189,7 +209,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); // Setup authentications (key: authentication name, value: authentication). - authentications = new HashMap(); + authentications = new HashMap<>(); Authentication auth = null; {{#authMethods}} if (authMap != null) { @@ -235,7 +255,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { authentications = Collections.unmodifiableMap(authentications); // Setup authentication lookup (key: authentication alias, value: authentication name) - authenticationLookup = new HashMap();{{#authMethods}}{{#vendorExtensions.x-auth-id-alias}} + authenticationLookup = new HashMap<>();{{#authMethods}}{{#vendorExtensions.x-auth-id-alias}} authenticationLookup.put("{{name}}", "{{.}}");{{/vendorExtensions.x-auth-id-alias}}{{/authMethods}} } @@ -251,7 +271,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Getter for the field httpClient.

* - * @return a {@link jakarta.ws.rs.client.Client} object. + * @return a {@link {{javaxPackage}}.ws.rs.client.Client} object. */ public Client getHttpClient() { return httpClient; @@ -260,8 +280,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Setter for the field httpClient.

* - * @param httpClient a {@link jakarta.ws.rs.client.Client} object. - * @return a {@link org.openapitools.client.ApiClient} object. + * @param httpClient a {@link {{javaxPackage}}.ws.rs.client.Client} object. + * @return a {@link ApiClient} object. */ public ApiClient setHttpClient(Client httpClient) { this.httpClient = httpClient; @@ -281,7 +301,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Sets the base URL to the location where the OpenAPI document is being served. * * @param basePath The base URL to the target host. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setBasePath(String basePath) { this.basePath = basePath; @@ -304,7 +324,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field servers.

* * @param servers a {@link java.util.List} of servers. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServers(List servers) { this.servers = servers; @@ -325,7 +345,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field serverIndex.

* * @param serverIndex the server index - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServerIndex(Integer serverIndex) { this.serverIndex = serverIndex; @@ -346,7 +366,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { *

Setter for the field serverVariables.

* * @param serverVariables a {@link java.util.Map} of server variables. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setServerVariables(Map serverVariables) { this.serverVariables = serverVariables; @@ -354,14 +374,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return this; } - private void updateBasePath() { + protected void updateBasePath() { if (serverIndex != null) { setBasePath(servers.get(serverIndex).URL(serverVariables)); } } {{#hasOAuthMethods}} - private void setOauthBasePath(String basePath) { + protected void setOauthBasePath(String basePath) { for(Authentication auth : authentications.values()) { if (auth instanceof OAuth) { ((OAuth) auth).setBasePath(basePath); @@ -393,7 +413,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set username for the first HTTP basic authentication. * * @param username Username - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setUsername(String username) { for (Authentication auth : authentications.values()) { @@ -409,7 +429,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set password for the first HTTP basic authentication. * * @param password Password - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setPassword(String password) { for (Authentication auth : authentications.values()) { @@ -425,7 +445,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set API key value for the first API key authentication. * * @param apiKey API key - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setApiKey(String apiKey) { for (Authentication auth : authentications.values()) { @@ -441,7 +461,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to configure authentications which respects aliases of API keys. * * @param secrets Hash map from authentication name to its secret. - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient configureApiKeys(Map secrets) { for (Map.Entry authEntry : authentications.entrySet()) { @@ -449,9 +469,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { if (auth instanceof ApiKeyAuth) { String name = authEntry.getKey(); // respect x-auth-id-alias property - name = authenticationLookup.containsKey(name) ? authenticationLookup.get(name) : name; - if (secrets.containsKey(name)) { - ((ApiKeyAuth) auth).setApiKey(secrets.get(name)); + name = authenticationLookup.getOrDefault(name, name); + String secret = secrets.get(name); + if (secret != null) { + ((ApiKeyAuth) auth).setApiKey(secret); } } } @@ -462,7 +483,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set API key prefix for the first API key authentication. * * @param apiKeyPrefix API key prefix - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setApiKeyPrefix(String apiKeyPrefix) { for (Authentication auth : authentications.values()) { @@ -478,7 +499,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set bearer token for the first Bearer authentication. * * @param bearerToken Bearer token - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setBearerToken(String bearerToken) { for (Authentication auth : authentications.values()) { @@ -495,7 +516,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set access token for the first OAuth2 authentication. * * @param accessToken Access token - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setAccessToken(String accessToken) { for (Authentication auth : authentications.values()) { @@ -512,7 +533,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param clientId the client ID * @param clientSecret the client secret - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthCredentials(String clientId, String clientSecret) { for (Authentication auth : authentications.values()) { @@ -528,7 +549,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the credentials of a public client for the first OAuth2 authentication. * * @param clientId the client ID - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthCredentialsForPublicClient(String clientId) { for (Authentication auth : authentications.values()) { @@ -545,7 +566,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param username the user name * @param password the user password - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthPasswordFlow(String username, String password) { for (Authentication auth : authentications.values()) { @@ -561,7 +582,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the authorization code flow for the first OAuth2 authentication. * * @param code the authorization code - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthAuthorizationCodeFlow(String code) { for (Authentication auth : authentications.values()) { @@ -577,7 +598,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Helper method to set the scopes for the first OAuth2 authentication. * * @param scope the oauth scope - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setOauthScope(String scope) { for (Authentication auth : authentications.values()) { @@ -594,7 +615,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the User-Agent header's value (by adding to the default header map). * * @param userAgent Http user agent - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setUserAgent(String userAgent) { this.userAgent = userAgent; @@ -616,7 +637,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param key The header's key * @param value The header's value - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient addDefaultHeader(String key, String value) { defaultHeaderMap.put(key, value); @@ -628,7 +649,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param key The cookie's key * @param value The cookie's value - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient addDefaultCookie(String key, String value) { defaultCookieMap.put(key, value); @@ -648,7 +669,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the client config. * * @param clientConfig Set the client config - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setClientConfig(ClientConfig clientConfig) { this.clientConfig = clientConfig; @@ -670,10 +691,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Enable/disable debugging for this API client. * * @param debugging To enable (true) or disable (false) debugging - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setDebugging(boolean debugging) { this.debugging = debugging; + applyDebugSetting(this.clientConfig); // Rebuild HTTP Client according to the new "debugging" value. this.httpClient = buildHttpClient(); return this; @@ -694,7 +716,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set temp folder path * * @param tempFolderPath Temp folder path - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setTempFolderPath(String tempFolderPath) { this.tempFolderPath = tempFolderPath; @@ -716,7 +738,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * {@link Integer#MAX_VALUE}. * * @param connectionTimeout Connection timeout in milliseconds - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setConnectTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; @@ -739,7 +761,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * {@link Integer#MAX_VALUE}. * * @param readTimeout Read timeout in milliseconds - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; @@ -760,7 +782,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Set the date format used to parse/format date parameters. * * @param dateFormat Date format - * @return a {@link org.openapitools.client.ApiClient} object. + * @return a {@link ApiClient} object. */ public ApiClient setDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; @@ -806,9 +828,9 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return formatDate((Date) param); } {{#jsr310}}else if (param instanceof OffsetDateTime) { return formatOffsetDateTime((OffsetDateTime) param); - } {{/jsr310}}else if (param instanceof Collection) { + } {{/jsr310}}else if (param instanceof Collection) { StringBuilder b = new StringBuilder(); - for(Object o : (Collection)param) { + for(Object o : (Collection)param) { if(b.length() > 0) { b.append(','); } @@ -829,14 +851,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return List of pairs */ public List parameterToPairs(String collectionFormat, String name, Object value){ - List params = new ArrayList(); + List params = new ArrayList<>(); // preconditions if (name == null || name.isEmpty() || value == null) return params; - Collection valueCollection; - if (value instanceof Collection) { - valueCollection = (Collection) value; + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; } else { params.add(new Pair(name, parameterToString(value))); return params; @@ -888,14 +910,13 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * application/json; charset=UTF8 * APPLICATION/JSON * application/vnd.company+json - * "* / *" is also default to JSON + * "*{@literal /}*" is also considered JSON by this method. * * @param mime MIME * @return True if the MIME type is JSON */ public boolean isJsonMime(String mime) { - String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; - return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + return mime != null && (mime.equals("*/*") || JSON_MIME_PATTERN.matcher(mime).matches()); } /** @@ -907,8 +928,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @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) { + public String selectHeaderAccept(String... accepts) { + if (accepts == null || accepts.length == 0) { return null; } for (String accept : accepts) { @@ -928,8 +949,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return The Content-Type header to use. If the given array is empty, * JSON will be used. */ - public String selectHeaderContentType(String[] contentTypes) { - if (contentTypes.length == 0) { + public String selectHeaderContentType(String... contentTypes) { + if (contentTypes == null || contentTypes.length == 0) { return "application/json"; } for (String contentType : contentTypes) { @@ -969,14 +990,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { if (contentType.startsWith("multipart/form-data")) { MultiPart multiPart = new MultiPart(); for (Entry param: formParams.entrySet()) { - if (param.getValue() instanceof File) { - File file = (File) param.getValue(); - FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()) - .fileName(file.getName()).size(file.length()).build(); - multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + if (param.getValue() instanceof Iterable) { + ((Iterable)param.getValue()).forEach(v -> addParamToMultipart(v, param.getKey(), multiPart)); } else { - FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()).build(); - multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(param.getValue()))); + addParamToMultipart(param.getValue(), param.getKey(), multiPart); } } entity = Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE); @@ -1005,6 +1022,36 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return entity; } + /** + * Adds the object with the provided key to the MultiPart. + * Based on the object type sets Content-Disposition and Content-Type. + * + * @param obj Object + * @param key Key of the object + * @param multiPart MultiPart to add the form param to + */ + protected void addParamToMultipart(Object value, String key, MultiPart multiPart) { + if (value instanceof File) { + File file = (File) value; + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key) + .fileName(file.getName()).size(file.length()).build(); + + // Attempt to probe the content type for the file so that the form part is more correctly + // and precisely identified, but fall back to application/octet-stream if that fails. + MediaType type; + try { + type = MediaType.valueOf(Files.probeContentType(file.toPath())); + } catch (IOException | IllegalArgumentException e) { + type = MediaType.APPLICATION_OCTET_STREAM_TYPE; + } + + multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, type)); + } else { + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build(); + multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value))); + } + } + /** * Serialize the given Java object into string according the given * Content-Type (only JSON, HTTP form is supported for now). @@ -1067,11 +1114,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return file; } - String contentType = null; - List contentTypes = response.getHeaders().get("Content-Type"); - if (contentTypes != null && !contentTypes.isEmpty()) - contentType = String.valueOf(contentTypes.get(0)); - // read the entity stream multiple times response.bufferEntity(); @@ -1098,7 +1140,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Prepare the file for download from the response.

* - * @param response a {@link jakarta.ws.rs.core.Response} object. + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. * @return a {@link java.io.File} object. * @throws java.io.IOException if any. */ @@ -1173,17 +1215,15 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { boolean isBodyNullable) throws ApiException { - // Not using `.target(targetURL).path(path)` below, - // to support (constant) query string in `path`, e.g. "/posts?draft=1" String targetURL; - if (serverIndex != null && operationServers.containsKey(operation)) { - Integer index = operationServerIndex.containsKey(operation) ? operationServerIndex.get(operation) : serverIndex; - Map variables = operationServerVariables.containsKey(operation) ? - operationServerVariables.get(operation) : serverVariables; - List serverConfigurations = operationServers.get(operation); + List serverConfigurations; + if (serverIndex != null && (serverConfigurations = operationServers.get(operation)) != null) { + int index = operationServerIndex.getOrDefault(operation, serverIndex).intValue(); + Map variables = operationServerVariables.getOrDefault(operation, serverVariables); if (index < 0 || index >= serverConfigurations.size()) { throw new ArrayIndexOutOfBoundsException( String.format( + java.util.Locale.ROOT, "Invalid index %d when selecting the host settings. Must be less than %d", index, serverConfigurations.size())); } @@ -1191,8 +1231,31 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } else { targetURL = this.basePath + path; } + // Not using `.target(targetURL).path(path)` below, + // to support (constant) query string in `path`, e.g. "/posts?draft=1" WebTarget target = httpClient.target(targetURL); + // put all headers in one place + Map allHeaderParams = new HashMap<>(defaultHeaderMap); + allHeaderParams.putAll(headerParams); + + if (authNames != null) { + // update different parameters (e.g. headers) for authentication + updateParamsForAuth( + authNames, + queryParams, + allHeaderParams, + cookieParams, + {{#hasHttpSignatureMethods}} + serializeToString(body, formParams, contentType, isBodyNullable), + {{/hasHttpSignatureMethods}} + {{^hasHttpSignatureMethods}} + null, + {{/hasHttpSignatureMethods}} + method, + target.getUri()); + } + if (queryParams != null) { for (Pair queryParam : queryParams) { if (queryParam.getValue() != null) { @@ -1201,11 +1264,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - Invocation.Builder invocationBuilder; + Invocation.Builder invocationBuilder = target.request(); + if (accept != null) { - invocationBuilder = target.request().accept(accept); - } else { - invocationBuilder = target.request(); + invocationBuilder = invocationBuilder.accept(accept); } for (Entry entry : cookieParams.entrySet()) { @@ -1224,20 +1286,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { Entity entity = serialize(body, formParams, contentType, isBodyNullable); - // put all headers in one place - Map allHeaderParams = new HashMap<>(defaultHeaderMap); - allHeaderParams.putAll(headerParams); - - // update different parameters (e.g. headers) for authentication - updateParamsForAuth( - authNames, - queryParams, - allHeaderParams, - cookieParams, - serializeToString(body, formParams, contentType, isBodyNullable), - method, - target.getUri()); - for (Entry entry : allHeaderParams.entrySet()) { String value = entry.getValue(); if (value != null) { @@ -1252,7 +1300,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { {{#hasOAuthMethods}} // If OAuth is used and a status 401 is received, renew the access token and retry the request - if (response.getStatusInfo() == Status.UNAUTHORIZED) { + if (authNames != null && response.getStatusInfo().getStatusCode() == Status.UNAUTHORIZED.getStatusCode()) { for (String authName : authNames) { Authentication authentication = authentications.get(authName); if (authentication instanceof OAuth) { @@ -1268,10 +1316,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } {{/hasOAuthMethods}} - int statusCode = response.getStatusInfo().getStatusCode(); + final int statusCode = response.getStatusInfo().getStatusCode(); + Map> responseHeaders = buildResponseHeaders(response); - if (response.getStatusInfo() == Status.NO_CONTENT) { + if (statusCode == Status.NO_CONTENT.getStatusCode()) { return new ApiResponse(statusCode, responseHeaders); } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { if (returnType == null) { @@ -1303,14 +1352,18 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - private Response sendRequest(String method, Invocation.Builder invocationBuilder, Entity entity) { + protected Response sendRequest(String method, Invocation.Builder invocationBuilder, Entity entity) { Response response; if ("POST".equals(method)) { response = invocationBuilder.post(entity); } else if ("PUT".equals(method)) { response = invocationBuilder.put(entity); } else if ("DELETE".equals(method)) { - response = invocationBuilder.method("DELETE", entity); + if ("".equals(entity.getEntity())) { + response = invocationBuilder.method("DELETE"); + } else { + response = invocationBuilder.method("DELETE", entity); + } } else if ("PATCH".equals(method)) { response = invocationBuilder.method("PATCH", entity); } else { @@ -1333,12 +1386,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return Client */ protected Client buildHttpClient() { - // recreate the client config to pickup changes - clientConfig = getDefaultClientConfig(); + // Create ClientConfig if it has not been initialized yet + if (clientConfig == null) { + clientConfig = getDefaultClientConfig(); + } ClientBuilder clientBuilder = ClientBuilder.newBuilder(); - customizeClientBuilder(clientBuilder); clientBuilder = clientBuilder.withConfig(clientConfig); + customizeClientBuilder(clientBuilder); return clientBuilder.build(); } @@ -1355,6 +1410,11 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { clientConfig.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // turn off compliance validation to be able to send payloads with DELETE calls clientConfig.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); + applyDebugSetting(clientConfig); + return clientConfig; + } + + protected void applyDebugSetting(ClientConfig clientConfig) { if (debugging) { clientConfig.register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), java.util.logging.Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024*50 /* Log payloads up to 50K */)); clientConfig.property(LoggingFeature.LOGGING_FEATURE_VERBOSITY, LoggingFeature.Verbosity.PAYLOAD_ANY); @@ -1364,8 +1424,6 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // suppress warnings for payloads with DELETE calls: java.util.logging.Logger.getLogger("org.glassfish.jersey.client").setLevel(java.util.logging.Level.SEVERE); } - - return clientConfig; } /** @@ -1382,7 +1440,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * To completely disable certificate validation (at your own risk), you can * override this method and invoke disableCertificateValidation(clientBuilder). * - * @param clientBuilder a {@link jakarta.ws.rs.client.ClientBuilder} object. + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. */ protected void customizeClientBuilder(ClientBuilder clientBuilder) { // No-op extension point @@ -1394,7 +1452,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * Please note that trusting all certificates is extremely risky. * This may be useful in a development environment with self-signed certificates. * - * @param clientBuilder a {@link jakarta.ws.rs.client.ClientBuilder} object. + * @param clientBuilder a {@link {{javaxPackage}}.ws.rs.client.ClientBuilder} object. * @throws java.security.KeyManagementException if any. * @throws java.security.NoSuchAlgorithmException if any. */ @@ -1421,14 +1479,14 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** *

Build the response headers.

* - * @param response a {@link jakarta.ws.rs.core.Response} object. + * @param response a {@link {{javaxPackage}}.ws.rs.core.Response} object. * @return a {@link java.util.Map} of response headers. */ protected Map> buildResponseHeaders(Response response) { - Map> responseHeaders = new HashMap>(); + Map> responseHeaders = new HashMap<>(); for (Entry> entry: response.getHeaders().entrySet()) { List values = entry.getValue(); - List headers = new ArrayList(); + List headers = new ArrayList<>(); for (Object o : values) { headers.add(String.valueOf(o)); } @@ -1457,4 +1515,4 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { auth.applyToParams(queryParams, headerParams, cookieParams, payload, method, uri); } } -} +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/JSON.mustache index aea0628ac..55b6e232c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/JSON.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.annotation.*; @@ -10,40 +12,39 @@ 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; -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.ext.ContextResolver; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.ext.ContextResolver; {{>generatedAnnotation}} + public class JSON implements ContextResolver { private ObjectMapper mapper; public JSON() { - mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - JsonMapper.builder().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}} + mapper = JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .defaultDateFormat(new RFC3339DateFormat()) + .addModule(new JavaTimeModule()) + {{#joda}} + .addModule(new JodaModule()) + {{/joda}} + {{#openApiNullable}} + .addModule(new JsonNullableModule()) + {{/openApiNullable}} + .addModule(new RFC3339JavaTimeModule()) + .build(); } /** @@ -76,7 +77,7 @@ public class JSON implements ContextResolver { public static Class getClassForElement(JsonNode node, Class modelClass) { ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass); if (cdm != null) { - return cdm.getClassForElement(node, new HashSet>()); + return cdm.getClassForElement(node, new HashSet<>()); } return null; } @@ -96,7 +97,7 @@ public class JSON implements ContextResolver { ClassDiscriminatorMapping(Class cls, String propertyName, Map> mappings) { modelClass = cls; discriminatorName = propertyName; - discriminatorMappings = new HashMap>(); + discriminatorMappings = new HashMap<>(); if (mappings != null) { discriminatorMappings.putAll(mappings); } @@ -191,9 +192,9 @@ public class JSON implements ContextResolver { visitedClasses.add(modelClass); // Traverse the oneOf/anyOf composed schemas. - Map descendants = modelDescendants.get(modelClass); + Map> descendants = modelDescendants.get(modelClass); if (descendants != null) { - for (GenericType childType : descendants.values()) { + for (GenericType childType : descendants.values()) { if (isInstanceOf(childType.getRawType(), inst, visitedClasses)) { return true; } @@ -205,12 +206,12 @@ public class JSON implements ContextResolver { /** * A map of discriminators for all model classes. */ - private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap, ClassDiscriminatorMapping>(); + private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>(); /** * A map of oneOf/anyOf descendants for each model class. */ - private static Map, Map> modelDescendants = new HashMap, Map>(); + private static Map, Map>> modelDescendants = new HashMap<>(); /** * Register a model class discriminator. @@ -230,7 +231,7 @@ public class JSON implements ContextResolver { * @param modelClass the model class * @param descendants a map of oneOf/anyOf descendants. */ - public static void registerDescendants(Class modelClass, Map descendants) { + public static void registerDescendants(Class modelClass, Map> descendants) { modelDescendants.put(modelClass, descendants); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/additional_properties.mustache index 61973dc24..2955e9392 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/additional_properties.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/additional_properties.mustache @@ -13,7 +13,7 @@ @JsonAnySetter public {{classname}} putAdditionalProperty(String key, {{{.}}} value) { if (this.additionalProperties == null) { - this.additionalProperties = new HashMap(); + this.additionalProperties = new HashMap<>(); } this.additionalProperties.put(key, value); return this; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/anyof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/anyof_model.mustache index 2e14233bb..ddd314493 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/anyof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/anyof_model.mustache @@ -1,5 +1,5 @@ -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.Response; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -24,7 +24,7 @@ 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -57,7 +57,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im Object deserialized = null; {{#discriminator}} - Class cls = JSON.getClassForElement(tree, {{classname}}.class); + Class cls = JSON.getClassForElement(tree, new {{classname}}().getClass()); if (cls != null) { // When the OAS schema includes a discriminator, use the discriminator value to // discriminate the anyOf schemas. @@ -68,10 +68,23 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return ret; } {{/discriminator}} + {{#composedSchemas}} {{#anyOf}} - // deserialize {{{.}}} + // deserialize {{{dataType}}}{{#isNullable}} (nullable){{/isNullable}} try { - deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + {{^isArray}} + {{^isMap}} + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{dataType}}}.class); + {{/isMap}} + {{/isArray}} + {{#isArray}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isArray}} + {{#isMap}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isMap}} {{classname}} ret = new {{classname}}(); ret.setActualInstance(deserialized); return ret; @@ -81,7 +94,8 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/anyOf}} - throw new IOException(String.format("Failed deserialization for {{classname}}: no match found")); + {{/composedSchemas}} + throw new IOException("Failed deserialization for {{classname}}: no match found"); } /** @@ -99,12 +113,13 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } // store a list of schema names defined in anyOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap<>(); public {{classname}}() { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } {{> libraries/jersey2/additional_properties }} + {{#additionalPropertiesType}} /** * Return true if this {{name}} object is equal to o. @@ -119,13 +134,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); } {{/additionalPropertiesType}} + {{#composedSchemas}} {{#anyOf}} - public {{classname}}({{{.}}} o) { + {{^vendorExtensions.x-duplicated-data-type}} + public {{classname}}({{{baseType}}} o) { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} static { {{#anyOf}} schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { @@ -134,7 +153,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); {{#discriminator}} // Initialize and register the discriminator mappings. - Map> mappings = new HashMap>(); + Map> mappings = new HashMap<>(); {{#mappedModels}} mappings.put("{{mappingName}}", {{modelName}}.class); {{/mappedModels}} @@ -144,7 +163,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -165,13 +184,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#anyOf}} - if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + {{^vendorExtensions.x-duplicated-data-type}} + if (JSON.isInstanceOf({{{baseType}}}.class, instance, new HashSet<>())) { super.setActualInstance(instance); return; } + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}"); } @@ -186,17 +209,21 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return super.getActualInstance(); } + {{#composedSchemas}} {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/anyOf}} + {{/composedSchemas}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api.mustache index 4ba6a6e95..c663d7580 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api.mustache @@ -6,19 +6,24 @@ import {{invokerPackage}}.ApiResponse; import {{invokerPackage}}.Configuration; import {{invokerPackage}}.Pair; -import jakarta.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.GenericType; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { private ApiClient apiClient; @@ -63,7 +68,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -81,7 +87,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { {{#returnType}}return {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}}.getData(){{/returnType}}; } {{/vendorExtensions.x-group-parameters}} @@ -97,7 +103,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -115,68 +122,95 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#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 + public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#hasRequiredParams}} + // Check required parameters + {{#allParams}} + {{#required}} 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}}; + {{/required}} + {{/allParams}} - // query params - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + {{/hasRequiredParams}} + {{#hasPathParams}} + // Path parameters + String localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll({{=% %=}}"\\{%baseName%}"%={{ }}=%, apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + {{/hasPathParams}} {{#queryParams}} + {{#-first}} + // Query parameters + List localVarQueryParams = new ArrayList<>( + apiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}}) + ); + {{/-first}} + {{^-first}} localVarQueryParams.addAll(apiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}})); + {{/-first}} + {{#-last}} + + {{/-last}} {{/queryParams}} + {{#headerParams}} + {{#-first}} + // Header parameters + Map localVarHeaderParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^required}} + }{{/required}} + {{#-last}} - {{#headerParams}}if ({{paramName}} != null) - localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/-last}} {{/headerParams}} + {{#cookieParams}} + {{#-first}} + // Cookie parameters + Map localVarCookieParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^required}} + }{{/required}} + {{#-last}} - {{#cookieParams}}if ({{paramName}} != null) - localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/-last}} {{/cookieParams}} + {{#formParams}} + {{#-first}} + // Form parameters + Map localVarFormParams = new LinkedHashMap<>(); + {{/-first}} + {{^required}}if ({{paramName}} != null) { + {{/required}}localVarFormParams.put("{{baseName}}", {{paramName}});{{^required}} + }{{/required}} + {{#-last}} - {{#formParams}}if ({{paramName}} != null) - localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/-last}} {{/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}} }; - + String localVarAccept = apiClient.selectHeaderAccept({{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}); + String localVarContentType = apiClient.selectHeaderContentType({{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}); + {{#hasAuthMethods}} + String[] localVarAuthNames = {{=% %=}}new String[] {%#authMethods%"%name%"%^-last%, %/-last%%/authMethods%};%={{ }}=% + {{/hasAuthMethods}} {{#returnType}} GenericType<{{{returnType}}}> localVarReturnType = new GenericType<{{{returnType}}}>() {}; - {{/returnType}} - return apiClient.invokeAPI("{{classname}}.{{operationId}}", localVarPath, "{{httpMethod}}", localVarQueryParams, localVarPostBody, - localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, - localVarAuthNames, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}}, {{#bodyParam}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/bodyParam}}{{^bodyParam}}false{{/bodyParam}}); + return apiClient.invokeAPI("{{classname}}.{{operationId}}", {{#hasPathParams}}localVarPath{{/hasPathParams}}{{^hasPathParams}}"{{{path}}}"{{/hasPathParams}}, "{{httpMethod}}", {{#queryParams}}{{#-first}}localVarQueryParams{{/-first}}{{/queryParams}}{{^queryParams}}new ArrayList<>(){{/queryParams}}, {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, + {{#headerParams}}{{#-first}}localVarHeaderParams{{/-first}}{{/headerParams}}{{^headerParams}}new LinkedHashMap<>(){{/headerParams}}, {{#cookieParams}}{{#-first}}localVarCookieParams{{/-first}}{{/cookieParams}}{{^cookieParams}}new LinkedHashMap<>(){{/cookieParams}}, {{#formParams}}{{#-first}}localVarFormParams{{/-first}}{{/formParams}}{{^formParams}}new LinkedHashMap<>(){{/formParams}}, localVarAccept, localVarContentType, + {{#hasAuthMethods}}localVarAuthNames{{/hasAuthMethods}}{{^hasAuthMethods}}null{{/hasAuthMethods}}, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}}, {{#bodyParam}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/bodyParam}}{{^bodyParam}}false{{/bodyParam}}); } {{#vendorExtensions.x-group-parameters}} public class API{{operationId}}Request { {{#allParams}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{dataType}}} {{paramName}}; {{/allParams}} - private API{{operationId}}Request({{#pathParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) { + private API{{operationId}}Request({{#pathParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) { {{#pathParams}} this.{{paramName}} = {{paramName}}; {{/pathParams}} @@ -189,7 +223,7 @@ public class {{classname}} { * @param {{paramName}} {{description}} ({{^required}}optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}{{/required}}{{#required}}required{{/required}}) * @return API{{operationId}}Request */ - public API{{operationId}}Request {{paramName}}({{{dataType}}} {{paramName}}) { + public API{{operationId}}Request {{paramName}}({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { this.{{paramName}} = {{paramName}}; return this; } @@ -202,7 +236,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -222,7 +257,8 @@ public class {{classname}} { * @throws ApiException if fails to make API call {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -253,7 +289,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public API{{operationId}}Request {{operationId}}({{#pathParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) throws ApiException { + public API{{operationId}}Request {{operationId}}({{#pathParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) throws ApiException { return new API{{operationId}}Request({{#pathParams}}{{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}); } {{/vendorExtensions.x-group-parameters}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/apiException.mustache index 74a419aac..d957acd81 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/apiException.mustache @@ -13,7 +13,10 @@ import java.util.TreeMap; * API Exception */ {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private Map> responseHeaders = null; private String responseBody = null; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api_test.mustache index eb5101b1a..926ba0a82 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/api_test.mustache @@ -11,13 +11,16 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/ApiKeyAuth.mustache index 1731f936f..82576065f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/ApiKeyAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/Authentication.mustache index 46d9c1ab6..169e3abdc 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/Authentication.mustache @@ -9,6 +9,8 @@ import java.net.URI; import java.util.Map; import java.util.List; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and query params. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBasicAuth.mustache index 13cecfa90..2a8774b6a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBasicAuth.mustache @@ -13,6 +13,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBearerAuth.mustache index 110a11ea7..cb78e4799 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/HttpBearerAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; private String bearerToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/OAuth.mustache index 67f565976..4725badff 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/auth/OAuth.mustache @@ -10,7 +10,7 @@ import com.github.scribejava.core.exceptions.OAuthException; import com.github.scribejava.core.model.OAuth2AccessToken; import com.github.scribejava.core.oauth.OAuth20Service; -import jakarta.ws.rs.core.UriBuilder; +import {{javaxPackage}}.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -21,6 +21,7 @@ import java.util.logging.Level; import java.util.logging.Logger; {{>generatedAnnotation}} + public class OAuth implements Authentication { private static final Logger log = Logger.getLogger(OAuth.class.getName()); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.gradle.mustache index 55c2dfd81..5b495f53a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.gradle.mustache @@ -58,9 +58,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -99,12 +99,15 @@ if(hasProperty('target') && target == 'android') { ext { swagger_annotations_version = "1.6.5" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "2.1.0" + {{#useBeanValidation}} + bean_validation_version = "3.0.2" + {{/useBeanValidation}} jersey_version = "3.0.4" junit_version = "5.8.2" {{#hasOAuthMethods}} @@ -113,6 +116,9 @@ ext { {{#hasHttpSignatureMethods}} tomitribe_http_signatures_version = "1.7" {{/hasHttpSignatureMethods}} + {{#useReflectionEqualsHashCode}} + commons_lang3_version = "3.17.0" + {{/useReflectionEqualsHashCode}} } dependencies { @@ -140,6 +146,12 @@ dependencies { implementation "org.tomitribe:tomitribe-http-signatures:$tomitribe_http_signatures_version" {{/hasHttpSignatureMethods}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$bean_validation_version" + {{/useBeanValidation}} + {{#useReflectionEqualsHashCode}} + implementation "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/useReflectionEqualsHashCode}} testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.sbt.mustache index ee811eb8c..173731f09 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/build.sbt.mustache @@ -3,7 +3,7 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), Compile / javacOptions ++= Seq("-Xlint:deprecation"), Compile / packageDoc / publishArtifact := false, @@ -16,15 +16,15 @@ lazy val root = (project in file(".")). "org.glassfish.jersey.media" % "jersey-media-multipart" % "3.0.4", "org.glassfish.jersey.media" % "jersey-media-json-jackson" % "3.0.4", "org.glassfish.jersey.connectors" % "jersey-apache-connector" % "3.0.4", - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.2" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.19.2" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.19.2" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.19.2" % "compile", {{#joda}} - "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.13.2" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.19.2" % "compile", {{/joda}} - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.13.2" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.19.2" % "compile", {{#openApiNullable}} - "org.openapitools" % "jackson-databind-nullable" % "0.2.4" % "compile", + "org.openapitools" % "jackson-databind-nullable" % "0.2.9" % "compile", {{/openApiNullable}} {{#hasOAuthMethods}} "com.github.scribejava" % "scribejava-apis" % "8.3.1" % "compile", @@ -33,6 +33,9 @@ lazy val root = (project in file(".")). "org.tomitribe" % "tomitribe-http-signatures" % "1.7" % "compile", {{/hasHttpSignatureMethods}} "jakarta.annotation" % "jakarta.annotation-api" % "2.1.0" % "compile", + {{#useReflectionEqualsHashCode}} + "org.apache.commons" % "commons-lang3" % "3.17.0" % "compile", + {{/useReflectionEqualsHashCode}} "org.junit.jupiter" % "junit-jupiter-api" % "5.8.2" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/generatedAnnotation.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/generatedAnnotation.mustache index e4f874954..e05689d5f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/generatedAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/generatedAnnotation.mustache @@ -1 +1 @@ -@jakarta.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file +@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}, comments = "Generator version: {{generatorVersion}}") \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model.mustache index 914e1eb1e..2758278ac 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model.mustache @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; {{/model}} {{/models}} import java.util.Objects; -import java.util.Arrays; import java.util.Map; import java.util.HashMap; {{#imports}} @@ -36,15 +35,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; {{/vendorExtensions.x-has-readonly-properties}} {{/jackson}} {{#withXml}} -import javax.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.*; {{/withXml}} {{#parcelableModel}} import android.os.Parcelable; import android.os.Parcel; {{/parcelableModel}} {{#useBeanValidation}} -import javax.validation.constraints.*; -import javax.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} {{#performBeanValidation}} import org.hibernate.validator.constraints.*; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_doc.mustache index be1aedcf2..f17263f08 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_doc.mustache @@ -2,17 +2,21 @@ {{#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}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_test.mustache index 2d4ccdd1a..acd659b66 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/model_test.mustache @@ -9,13 +9,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{#fullJavaUtil}} -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -{{/fullJavaUtil}} /** * Model tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/oneof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/oneof_model.mustache index e59299829..87af00c8d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/oneof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/oneof_model.mustache @@ -1,5 +1,5 @@ -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.Response; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,7 +26,7 @@ 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -60,7 +60,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{#useOneOfDiscriminatorLookup}} {{#discriminator}} {{classname}} new{{classname}} = new {{classname}}(); - Map result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); + Map result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); String discriminatorValue = (String)result2.get("{{{propertyBaseName}}}"); switch (discriminatorValue) { {{#mappedModels}} @@ -70,7 +70,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im 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)); + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); } {{/discriminator}} @@ -78,43 +78,86 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); int match = 0; JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + {{#composedSchemas}} {{#oneOf}} - // deserialize {{{.}}} + // deserialize {{{dataType}}}{{#isNullable}} (nullable){{/isNullable}} try { + {{^isArray}} 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}} - } + {{#isPrimitiveType}} + attemptParsing = typeCoercion; //respect type coercion setting + if (!attemptParsing) { + {{#isString}} + attemptParsing |= (token == JsonToken.VALUE_STRING); + {{/isString}} + {{#isInteger}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isInteger}} + {{#isLong}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isLong}} + {{#isShort}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_INT); + {{/isShort}} + {{#isFloat}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isFloat}} + {{#isDouble}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isDouble}} + {{#isNumber}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isNumber}} + {{#isDecimal}} + attemptParsing |= (token == JsonToken.VALUE_NUMBER_FLOAT); + {{/isDecimal}} + {{#isBoolean}} + attemptParsing |= (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE); + {{/isBoolean}} + {{#isNullable}} + attemptParsing |= (token == JsonToken.VALUE_NULL); + {{/isNullable}} } + {{/isPrimitiveType}} if (attemptParsing) { - deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + {{#isMap}} + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + {{/isMap}} + {{^isMap}} + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{dataType}}}.class); + {{/isMap}} // 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 '{{{.}}}'"); + log.log(Level.FINER, "Input data matches schema '{{{dataType}}}'"); } + {{/isArray}} + {{#isArray}} + if (token == JsonToken.START_ARRAY) { + final TypeReference<{{{dataType}}}> ref = new TypeReference<{{{dataType}}}>(){}; + deserialized = tree.traverse(jp.getCodec()).readValueAs(ref); + // 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 '{{{dataType}}}'"); + } + {{/isArray}} } catch (Exception e) { // deserialization failed, continue - log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); + log.log(Level.FINER, "Input data does not match schema '{{{dataType}}}'", e); } {{/oneOf}} + {{/composedSchemas}} 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)); + throw new IOException(String.format(java.util.Locale.ROOT, "Failed deserialization for {{classname}}: %d classes match result, expected 1", match)); } /** @@ -132,12 +175,13 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } // store a list of schema names defined in oneOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap<>(); public {{classname}}() { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } {{> libraries/jersey2/additional_properties }} + {{#additionalPropertiesType}} /** * Return true if this {{name}} object is equal to o. @@ -152,13 +196,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); } {{/additionalPropertiesType}} + {{#composedSchemas}} {{#oneOf}} - public {{classname}}({{{.}}} o) { + {{^vendorExtensions.x-duplicated-data-type}} + public {{classname}}({{{baseType}}} o) { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} static { {{#oneOf}} schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { @@ -167,7 +215,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); {{#discriminator}} // Initialize and register the discriminator mappings. - Map> mappings = new HashMap>(); + Map> mappings = new HashMap<>(); {{#mappedModels}} mappings.put("{{mappingName}}", {{modelName}}.class); {{/mappedModels}} @@ -177,7 +225,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -198,13 +246,17 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#oneOf}} - if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + {{^vendorExtensions.x-duplicated-data-type}} + if (JSON.isInstanceOf({{{baseType}}}.class, instance, new HashSet<>())) { super.setActualInstance(instance); return; } + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); } @@ -219,17 +271,21 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return super.getActualInstance(); } + {{#composedSchemas}} {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/oneOf}} + {{/composedSchemas}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pojo.mustache index b9527bdd5..2f4bac6a7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pojo.mustache @@ -8,6 +8,11 @@ @ApiModel(description = "{{{.}}}") {{/description}} {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} {{#jackson}} @JsonPropertyOrder({ {{#vars}} @@ -32,11 +37,13 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{^isContainer}} {{^vendorExtensions.x-enum-as-string}} {{>modelInnerEnum}} + {{/vendorExtensions.x-enum-as-string}} {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} {{>modelInnerEnum}} + {{/mostInnerItems}} {{/isContainer}} {{/isEnum}} @@ -47,47 +54,37 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{/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}} + {{#deprecated}} + @Deprecated + {{/deprecated}} private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); {{/isContainer}} {{^isContainer}} + {{#deprecated}} + @Deprecated + {{/deprecated}} 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}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}}; - {{/isContainer}} - {{^isContainer}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/vars}} @@ -104,7 +101,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens ) { this(); {{#readOnlyVars}} - this.{{name}} = {{name}}; + 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}} }{{/jackson}}{{/withXml}}{{/vendorExtensions.x-has-readonly-properties}} {{#vars}} @@ -116,7 +113,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens )); {{/vendorExtensions.x-enum-as-string}} - public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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)); @@ -133,10 +133,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { {{#vendorExtensions.x-is-jackson-optional-nullable}} if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); } try { this.{{name}}.get().add({{name}}Item); @@ -146,11 +146,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.add({{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -158,10 +156,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isArray}} {{#isMap}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} put{{nameInPascalCase}}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}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); } try { this.{{name}}.get().put(key, {{name}}Item); @@ -171,11 +169,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.put(key, {{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -183,7 +179,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isMap}} {{/isReadOnly}} - /** + /** {{#description}} * {{.}} {{/description}} @@ -200,27 +196,21 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#deprecated}} * @deprecated {{/deprecated}} - **/ + */ {{#deprecated}} @Deprecated {{/deprecated}} -{{#required}} -{{#isNullable}} - @jakarta.annotation.Nullable -{{/isNullable}} -{{^isNullable}} - @jakarta.annotation.Nonnull -{{/isNullable}} -{{/required}} -{{^required}} - @jakarta.annotation.Nullable -{{/required}} + {{>nullable_var_annotations}}{{! prevent indent}} {{#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}} @@ -245,6 +235,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#vendorExtensions.x-is-jackson-optional-nullable}} {{> jackson_annotations}} + public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { return {{name}}; } @@ -257,8 +248,11 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^isReadOnly}} + {{#deprecated}} + @Deprecated + {{/deprecated}} {{#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-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullable_var_annotations}} {{{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)); @@ -276,6 +270,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/vars}} {{>libraries/jersey2/additional_properties}} + /** * Return true if this {{name}} object is equal to o. */ @@ -329,7 +324,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens sb.append(" ").append(toIndentedString(super.toString())).append("\n"); {{/parent}} {{#vars}} - sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); {{/vars}} {{#additionalPropertiesType}} sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); @@ -390,7 +385,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return 0; } - public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<>() { public {{classname}} createFromParcel(Parcel in) { {{#model}} {{#isArray}} @@ -409,14 +404,14 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens }; {{/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); -} + 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/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pom.mustache index 5df86ad01..1844bb424 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/jersey3/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 3.0.0-M5 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods 10 @@ -265,6 +265,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -372,6 +379,15 @@ jersey-apache-connector ${jersey-version} + {{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + {{/useReflectionEqualsHashCode}} + org.junit.jupiter @@ -382,22 +398,34 @@ UTF-8 + {{#swagger1AnnotationLibrary}} 1.6.6 - 3.0.4 - 2.13.4 - 2.13.4.2 - 0.2.4 - 2.1.0 - {{#useBeanValidation}} + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 3.1.11 + 2.19.2 + 2.19.2 + 0.2.9 + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} + 1.3.5 2.0.2 - {{/useBeanValidation}} - 5.8.2 + {{/useJakartaEe}} + 5.10.0 {{#hasHttpSignatureMethods}} - 1.7 + 1.8 {{/hasHttpSignatureMethods}} {{#hasOAuthMethods}} - 8.3.1 + 8.3.3 {{/hasOAuthMethods}} + {{#useReflectionEqualsHashCode}} + 3.17.0 + {{/useReflectionEqualsHashCode}} 2.21.0 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/README.mustache index 58d2d5f5c..377dfc2c9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/README.mustache @@ -1,10 +1,19 @@ -# {{appName}} - MicroProfile Rest Client +# {{appName}} - MicroProfile Rest Client & MicroProfile Server {{#appDescriptionWithNewLines}} {{{.}}} {{/appDescriptionWithNewLines}} +{{^microprofileServer}} ## Overview This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. [MicroProfile Rest Client](https://github.com/eclipse/microprofile-rest-client) is a type-safe way of calling REST services. The generated client contains an interface which acts as the client, you can inject it into dependent classes. +{{/microprofileServer}} + +{{#microprofileServer}} +## Overview +This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. +The generated server contains an interface which acts as the server, you can inject it into the controller class. +This module is intended to provide additional server features, like accessing an operations response object, when multiple responses where specified. +{{/microprofileServer}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/additional_properties.mustache new file mode 100644 index 000000000..538635ca0 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/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 + */ + @com.fasterxml.jackson.annotation.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 + */ + @com.fasterxml.jackson.annotation.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}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api.mustache index 49ad40447..5215f5b8c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api.mustache @@ -12,18 +12,22 @@ import java.util.Set; import {{rootJavaEEPackage}}.ws.rs.*; import {{rootJavaEEPackage}}.ws.rs.core.Response; import {{rootJavaEEPackage}}.ws.rs.core.MediaType; -{{^disableMultipart}} -import org.apache.cxf.jaxrs.ext.multipart.*; -{{/disableMultipart}} + +{{#microprofileMutiny}} +import io.smallrye.mutiny.Uni; +{{/microprofileMutiny}} {{#useBeanValidation}} -import javax.validation.constraints.*; -import javax.validation.Valid; +import {{rootJavaEEPackage}}.validation.constraints.*; +import {{rootJavaEEPackage}}.validation.Valid; {{/useBeanValidation}} +{{#microprofileRegisterExceptionMapper}} import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +{{/microprofileRegisterExceptionMapper}} import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + {{#appName}} /** * {{{appName}}} @@ -35,8 +39,12 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; */ {{/appName}} -@RegisterRestClient{{#configKey}}(configKey="{{configKey}}"){{/configKey}} +{{^microprofileServer}} +@RegisterRestClient{{#configKey}}(configKey="{{configKey}}"){{/configKey}}{{#configKeyFromClassName}}{{#operations}}(configKey="{{configKey}}"){{/operations}}{{/configKeyFromClassName}} +{{/microprofileServer}} +{{#microprofileRegisterExceptionMapper}} @RegisterProvider(ApiExceptionMapper.class) +{{/microprofileRegisterExceptionMapper}} @Path("{{#useAnnotatedBasePath}}{{contextPath}}{{/useAnnotatedBasePath}}{{commonPath}}") public interface {{classname}} { {{#operations}} @@ -66,7 +74,53 @@ public interface {{classname}} { {{#hasProduces}} @Produces({ {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }) {{/hasProduces}} - public {{{returnType}}}{{^returnType}}void{{/returnType}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException, ProcessingException; +{{^singleRequestParameter}} + {{^vendorExtensions.x-java-is-response-void}}{{#microprofileServer}}{{> server_operation}}{{/microprofileServer}}{{^microprofileServer}}{{> client_operation}}{{/microprofileServer}}{{/vendorExtensions.x-java-is-response-void}}{{#vendorExtensions.x-java-is-response-void}}{{#microprofileMutiny}}Uni{{/microprofileMutiny}}{{^microprofileMutiny}}void{{/microprofileMutiny}}{{/vendorExtensions.x-java-is-response-void}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException, ProcessingException; +{{/singleRequestParameter}} +{{#singleRequestParameter}} + {{^vendorExtensions.x-java-is-response-void}}{{#microprofileMutiny}}Uni<{{{returnType}}}>{{/microprofileMutiny}}{{^microprofileMutiny}}{{{returnType}}}{{/microprofileMutiny}}{{/vendorExtensions.x-java-is-response-void}}{{#vendorExtensions.x-java-is-response-void}}{{#microprofileMutiny}}Uni{{/microprofileMutiny}}{{^microprofileMutiny}}void{{/microprofileMutiny}}{{/vendorExtensions.x-java-is-response-void}} {{nickname}}({{#hasNonBodyParams}}@BeanParam {{operationIdCamelCase}}Request request{{/hasNonBodyParams}}{{#bodyParams}}{{#hasNonBodyParams}}, {{/hasNonBodyParams}}{{>bodyParams}}{{/bodyParams}}) throws ApiException, ProcessingException; + {{#hasNonBodyParams}} + public class {{operationIdCamelCase}}Request { + + {{#queryParams}} + private {{>queryParams}}; + {{/queryParams}} + {{#headerParams}} + private {{>headerParams}}; + {{/headerParams}} + {{#pathParams}} + private {{>pathParams}}; + {{/pathParams}} + {{#formParams}} + private {{>formParams}}; + {{/formParams}} + {{#cookieParams}} + private {{>cookieParams}}; + {{/cookieParams}} + + private {{operationIdCamelCase}}Request() { + } + + public static {{operationIdCamelCase}}Request newInstance() { + return new {{operationIdCamelCase}}Request(); + } + + {{#allParams}} + {{^isBodyParam}} + /** + * Set {{paramName}}{{>formParamsNameSuffix}} + * @param {{paramName}}{{>formParamsNameSuffix}} {{description}} ({{^required}}optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}{{/required}}{{#required}}required{{/required}}) + * @return {{operationIdCamelCase}}Request + */ + public {{operationIdCamelCase}}Request {{paramName}}{{>formParamsNameSuffix}}({{>queryParamsImpl}}{{>pathParamsImpl}}{{>headerParamsImpl}}{{>formParamsImpl}}{{>cookieParamsImpl}}) { + this.{{paramName}}{{>formParamsNameSuffix}} = {{paramName}}{{>formParamsNameSuffix}}; + return this; + } + {{/isBodyParam}} + {{/allParams}} + } + {{/hasNonBodyParams}} +{{/singleRequestParameter}} {{/operation}} } {{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception.mustache index af0ecc723..63a218675 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception.mustache @@ -3,7 +3,7 @@ package {{apiPackage}}; import {{rootJavaEEPackage}}.ws.rs.core.Response; -public class ApiException extends Exception { +public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ private static final long serialVersionUID = 1L; private Response response; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception_mapper.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception_mapper.mustache index 12988f5ed..2842b1c87 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception_mapper.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_exception_mapper.mustache @@ -3,10 +3,14 @@ package {{apiPackage}}; import {{rootJavaEEPackage}}.ws.rs.core.MultivaluedMap; import {{rootJavaEEPackage}}.ws.rs.core.Response; +{{#microprofileGlobalExceptionMapper}} import {{rootJavaEEPackage}}.ws.rs.ext.Provider; +{{/microprofileGlobalExceptionMapper}} import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; +{{#microprofileGlobalExceptionMapper}} @Provider +{{/microprofileGlobalExceptionMapper}} public class ApiExceptionMapper implements ResponseExceptionMapper { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_test.mustache index 9fce40aed..b124fa062 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/api_test.mustache @@ -4,23 +4,26 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Before; -import static org.junit.Assert.*; +{{#isFile}} +import java.io.File; +{{/isFile}} +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +{{#generateSpringBootApplication}} +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; +{{/generateSpringBootApplication}} import org.eclipse.microprofile.rest.client.RestClientBuilder; import java.net.URL; import java.net.MalformedURLException; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -{{/fullJavaUtil}} - - /** {{#appName}} @@ -30,7 +33,7 @@ import java.util.Set; * API tests for {{classname}} */ {{#generateSpringBootApplication}} -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @SpringApplicationConfiguration(classes = SpringBootApplication.class) @WebAppConfiguration @IntegrationTest("server.port=0") @@ -40,7 +43,7 @@ public class {{classname}}Test { private {{classname}} client; private String baseUrl = "http://localhost:9080"; - @Before + @BeforeEach public void setup() throws MalformedURLException { {{#microprofile3}} // TODO initialize the client @@ -70,10 +73,10 @@ public class {{classname}}Test { public void {{operationId}}Test() { // TODO: test validations {{#allParams}} - {{^isFile}}{{{dataType}}} {{paramName}} = null;{{/isFile}}{{#isFile}}org.apache.cxf.jaxrs.ext.multipart.Attachment {{paramName}} = null;{{/isFile}} + {{^isFile}}{{{dataType}}} {{paramName}} = null;{{/isFile}}{{#isFile}}File {{paramName}} = null;{{/isFile}} {{/allParams}} - //{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); - //{{#returnType}}assertNotNull(response);{{/returnType}} + //{{^vendorExtensions.x-java-is-response-void}}{{#microprofileMutiny}}Uni<{{{returnType}}}>{{/microprofileMutiny}}{{^microprofileMutiny}}{{{returnType}}}{{/microprofileMutiny}} response = {{/vendorExtensions.x-java-is-response-void}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + //{{#returnType}}Assertions.assertNotNull(response);{{/returnType}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCookieParams.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCookieParams.mustache new file mode 100644 index 000000000..c4ff01d7e --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCookieParams.mustache @@ -0,0 +1 @@ +{{#required}} @NotNull{{/required}}{{>beanValidationCore}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCore.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCore.mustache index b58f0a266..1b1b579bd 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCore.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/beanValidationCore.mustache @@ -1,4 +1,4 @@ -{{#pattern}} @Pattern(regexp="{{{.}}}"){{/pattern}}{{! +{{^isUuid}}{{#pattern}} @Pattern(regexp="{{{.}}}"){{/pattern}}{{! minLength && maxLength set }}{{#minLength}}{{#maxLength}} @Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{! minLength set, maxLength not @@ -17,4 +17,4 @@ isInteger set 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("{{.}}"){{/minimum}}{{#maximum}} @DecimalMax("{{.}}"){{/maximum}}{{/isLong}}{{/isInteger}} \ No newline at end of file +}}{{^isInteger}}{{^isLong}}{{#minimum}} @DecimalMin("{{.}}"){{/minimum}}{{#maximum}} @DecimalMax("{{.}}"){{/maximum}}{{/isLong}}{{/isInteger}}{{/isUuid}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/client_operation.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/client_operation.mustache new file mode 100644 index 000000000..403918ddc --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/client_operation.mustache @@ -0,0 +1 @@ +{{#microprofileMutiny}}Uni<{{{returnType}}}>{{/microprofileMutiny}}{{^microprofileMutiny}}{{{returnType}}}{{/microprofileMutiny}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParams.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParams.mustache new file mode 100644 index 000000000..4cca907c6 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParams.mustache @@ -0,0 +1 @@ +{{#isCookieParam}}@CookieParam("{{baseName}}") {{#useBeanValidation}}{{>beanValidationCookieParams}}{{/useBeanValidation}} {{{dataType}}} {{paramName}}{{/isCookieParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParamsImpl.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParamsImpl.mustache new file mode 100644 index 000000000..70871f0f8 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/cookieParamsImpl.mustache @@ -0,0 +1 @@ +{{#isCookieParam}}{{{dataType}}} {{paramName}}{{/isCookieParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumClass.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumClass.mustache index 38127a637..9315b9c9d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumClass.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumClass.mustache @@ -3,17 +3,19 @@ @XmlEnum({{dataType}}.class) {{/withXml}} {{^withXml}} + {{#jsonb}} @JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) @JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) + {{/jsonb}} {{/withXml}} - {{>additionalEnumTypeAnnotations}}public enum {{datatypeWithEnum}} { + {{>additionalEnumTypeAnnotations}}public enum {{datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#allowableValues}} {{#withXml}} {{#enumVars}}@XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{name}}({{dataType}}.valueOf({{{value}}})){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}} {{/withXml}} {{^withXml}} - {{#enumVars}}{{name}}({{dataType}}.valueOf({{{value}}})){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}} + {{#enumVars}}{{name}}({{^isUri}}{{dataType}}.valueOf({{/isUri}}{{{value}}}{{^isUri}}){{/isUri}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}} {{/withXml}} {{/allowableValues}} @@ -24,6 +26,9 @@ value = v; } + {{#jackson}} + @JsonValue + {{/jackson}} public {{dataType}} value() { return value; } @@ -44,6 +49,7 @@ } {{/withXml}} {{^withXml}} + {{#jsonb}} public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { @Override public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { @@ -59,8 +65,20 @@ public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { @Override public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { - generator.write(obj.value); + generator.write(obj.value{{#isUri}}.toASCIIString(){{/isUri}}); } } + {{/jsonb}} + {{#jackson}} + @JsonCreator + 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}} + } + {{/jackson}} {{/withXml}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumOuterClass.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumOuterClass.mustache index d458acb5a..40a295f3f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumOuterClass.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/enumOuterClass.mustache @@ -2,11 +2,18 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; {{/jackson}} +{{#isUri}} +import java.net.URI; +{{/isUri}} /** * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} */ -{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { +{{#jsonb}} +@JsonbTypeSerializer({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Serializer.class) +@JsonbTypeDeserializer({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Deserializer.class) +{{/jsonb}} +{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#gson}} {{#allowableValues}}{{#enumVars}} @SerializedName({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) @@ -19,7 +26,7 @@ import com.fasterxml.jackson.annotation.JsonValue; {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} {{/gson}} - private final {{{dataType}}} value; + private {{{dataType}}} value; {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { this.value = value; @@ -33,6 +40,22 @@ import com.fasterxml.jackson.annotation.JsonValue; return String.valueOf(value); } +{{#jsonb}} + public static final class Deserializer implements JsonbDeserializer<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + return fromValue(parser.getString()); + } + } + + public static final class Serializer implements JsonbSerializer<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public void serialize({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value{{#isUri}}.toASCIIString(){{/isUri}}); + } + } + +{{/jsonb}} {{#jackson}} @JsonCreator {{/jackson}} @@ -42,7 +65,6 @@ import com.fasterxml.jackson.annotation.JsonValue; return b; } } - {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + text + "'");{{/useNullForUnknownEnumValue}} + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + text + "'");{{/enumUnknownDefaultCase}}{{/useNullForUnknownEnumValue}} } - } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParams.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParams.mustache index 97216dc18..3ec6fb98c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParams.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParams.mustache @@ -1 +1 @@ -{{#isFormParam}}{{^isFile}}@Multipart(value = "{{baseName}}"{{^required}}, required = false{{/required}}) {{{dataType}}} {{paramName}}{{/isFile}}{{#isFile}} @Multipart(value = "{{baseName}}" {{^required}}, required = false{{/required}}) Attachment {{paramName}}Detail{{/isFile}}{{/isFormParam}} \ No newline at end of file +{{#isFormParam}}{{^isFile}}{{#required}}{{#useBeanValidation}}@NotNull {{/useBeanValidation}}{{/required}}@FormParam("{{baseName}}") {{{dataType}}} {{paramName}}{{/isFile}}{{#isFile}} {{#required}}{{#useBeanValidation}}@NotNull {{/useBeanValidation}}{{/required}}@FormParam("{{baseName}}") File {{paramName}}Detail{{/isFile}}{{/isFormParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsImpl.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsImpl.mustache index 2be40e1dd..1cdf27390 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsImpl.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsImpl.mustache @@ -1 +1 @@ -{{#isFormParam}}{{^isFile}}{{{dataType}}} {{paramName}}{{/isFile}}{{#isFile}} Attachment {{paramName}}Detail{{/isFile}}{{/isFormParam}} \ No newline at end of file +{{#isFormParam}}{{^isFile}}{{{dataType}}} {{paramName}}{{/isFile}}{{#isFile}} File {{paramName}}Detail{{/isFile}}{{/isFormParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsNameSuffix.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsNameSuffix.mustache new file mode 100644 index 000000000..a44f6d710 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/formParamsNameSuffix.mustache @@ -0,0 +1 @@ +{{#isFormParam}}{{#isFile}}Detail{{/isFile}}{{/isFormParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/generatedAnnotation.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/generatedAnnotation.mustache index 356a48872..cf058a0fa 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/generatedAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/generatedAnnotation.mustache @@ -1 +1 @@ -@{{rootJavaEEPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file +@{{rootJavaEEPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}, comments = "Generator version: {{generatorVersion}}") \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/kumuluzee.pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/kumuluzee.pom.mustache index ac235b7ed..195aa9d44 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/kumuluzee.pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/kumuluzee.pom.mustache @@ -18,7 +18,7 @@ 1.2.3 1.4.1 3.2.6 - 4.13 + 5.10.2 2.28 @@ -70,8 +70,8 @@ ${cxf-rt-rs-extension-providers.version} - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/licenseInfo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/licenseInfo.mustache index be193d0c4..c66209f27 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/licenseInfo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/licenseInfo.mustache @@ -1,4 +1,4 @@ -/** +/* * {{{appName}}} * {{{appDescription}}} * diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/model.mustache index 00a3c3db1..875c73f0f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/model.mustache @@ -1,11 +1,51 @@ {{>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; +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; +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 {{rootJavaEEPackage}}.xml.bind.annotation.*; +import {{rootJavaEEPackage}}.xml.bind.annotation.adapters.*; +{{/withXml}} +{{#jsonb}} +import java.lang.reflect.Type; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeDeserializer; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.DeserializationContext; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbDeserializer; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.SerializationContext; +import {{rootJavaEEPackage}}.json.stream.JsonGenerator; +import {{rootJavaEEPackage}}.json.stream.JsonParser; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbProperty; +{{#jsonbPolymorphism}} +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbSubtype; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTransient; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeInfo; +{{/jsonbPolymorphism}} +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; +{{/jsonb}} {{#useBeanValidation}} import {{rootJavaEEPackage}}.validation.constraints.*; import {{rootJavaEEPackage}}.validation.Valid; @@ -15,9 +55,11 @@ import {{rootJavaEEPackage}}.validation.Valid; {{#model}} {{#isEnum}} {{>enumOuterClass}} + {{/isEnum}} {{^isEnum}} {{>pojo}} + {{/isEnum}} {{/model}} -{{/models}} +{{/models}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojo.mustache index ddec54aae..36a0fd404 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojo.mustache @@ -1,42 +1,29 @@ {{#withXml}} -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlElement; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlRootElement; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlAccessType; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlAccessorType; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlType; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlEnum; -import {{rootJavaEEPackage}}.xml.bind.annotation.XmlEnumValue; -{{/withXml}} -{{^withXml}} -import java.lang.reflect.Type; -import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeDeserializer; -import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeSerializer; -import {{rootJavaEEPackage}}.json.bind.serializer.DeserializationContext; -import {{rootJavaEEPackage}}.json.bind.serializer.JsonbDeserializer; -import {{rootJavaEEPackage}}.json.bind.serializer.JsonbSerializer; -import {{rootJavaEEPackage}}.json.bind.serializer.SerializationContext; -import {{rootJavaEEPackage}}.json.stream.JsonGenerator; -import {{rootJavaEEPackage}}.json.stream.JsonParser; -import {{rootJavaEEPackage}}.json.bind.annotation.JsonbProperty; -{{#vendorExtensions.x-has-readonly-properties}} -import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/withXml}} - -{{#withXml}} -@XmlAccessorType(XmlAccessType.FIELD) -{{#hasVars}} @XmlType(name = "{{classname}}", propOrder = - { {{#vars}}"{{name}}"{{^-last}}, {{/-last}}{{/vars}} -}){{/hasVars}} +{{#hasVars}}@XmlType(name = "{{classname}}", propOrder = + { {{#vars}}"{{name}}"{{^-last}}, {{/-last}}{{/vars}} } +){{/hasVars}} {{^hasVars}}@XmlType(name = "{{classname}}"){{/hasVars}} -{{^parent}}@XmlRootElement(name="{{classname}}"){{/parent}} +{{> xmlAnnotation }} + {{/withXml}} +{{#jackson}} +@JsonPropertyOrder({ +{{#vars}} + {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} +{{/vars}} +}) +{{#isClassnameSanitized}} +{{^hasDiscriminatorWithNonEmptyMapping}} +@JsonTypeName("{{name}}") +{{/hasDiscriminatorWithNonEmptyMapping}} +{{/isClassnameSanitized}} +{{/jackson}} {{#description}} /** * {{{.}}} - **/ + */ {{/description}} -{{>additionalModelTypeAnnotations}} +{{>additionalModelTypeAnnotations}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}} {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} @@ -44,19 +31,25 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi {{#vars}}{{#isEnum}}{{^isContainer}} {{>enumClass}}{{/isContainer}}{{#isContainer}}{{#mostInnerItems}} {{>enumClass}}{{/mostInnerItems}}{{/isContainer}}{{/isEnum}} +{{#jackson}} + public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; +{{/jackson}} {{#withXml}} - @XmlElement(name="{{baseName}}"{{#required}}, required = {{required}}{{/required}}) + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{/withXml}} {{#description}} /** - * {{{.}}} - **/ + * {{{.}}} + */ {{/description}} {{^withXml}} - @JsonbProperty("{{baseName}}") + {{#jsonb}}{{^isDiscriminator}}@JsonbProperty("{{baseName}}"){{/isDiscriminator}}{{#isDiscriminator}}{{#jsonbPolymorphism}}@JsonbTransient{{/jsonbPolymorphism}}{{^jsonbPolymorphism}}@JsonbProperty("{{baseName}}"){{/jsonbPolymorphism}}{{/isDiscriminator}}{{/jsonb}} {{/withXml}} {{#vendorExtensions.x-field-extra-annotation}} -{{{vendorExtensions.x-field-extra-annotation}}} +{{{.}}} {{/vendorExtensions.x-field-extra-annotation}} {{#isContainer}} private {{{datatypeWithEnum}}} {{name}}{{#required}} = {{{defaultValue}}}{{/required}}{{^required}} = null{{/required}}; @@ -65,14 +58,16 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; {{/isContainer}} {{/vars}} +{{>additional_properties}} + {{#vendorExtensions.x-has-readonly-properties}}{{^withXml}} public {{classname}}() { } - @JsonbCreator + {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} public {{classname}}( {{#readOnlyVars}} - @JsonbProperty(value = "{{baseName}}"{{^required}}, nillable = true{{/required}}) {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nillable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}{{#required}}, required = true{{/required}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} {{/readOnlyVars}} ) { {{#readOnlyVars}} @@ -81,7 +76,7 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi } {{/withXml}}{{/vendorExtensions.x-has-readonly-properties}} {{#vars}} - /** + /** {{#description}} * {{.}} {{/description}} @@ -98,14 +93,14 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi {{#deprecated}} * @deprecated {{/deprecated}} - **/ + **/ {{#deprecated}} @Deprecated {{/deprecated}} {{#vendorExtensions.x-extra-annotation}} {{{vendorExtensions.x-extra-annotation}}} {{/vendorExtensions.x-extra-annotation}} -{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}} {{#withXml}}{{#isEnum}}{{^isArray}}{{^isMap}}public {{dataType}} {{getter}}() { +{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}}{{#jackson}}{{> jackson_annotations}}{{/jackson}} {{#withXml}}{{#isEnum}}{{^isArray}}{{^isMap}} public {{dataType}} {{getter}}() { if ({{name}} == null) { return null; } @@ -122,10 +117,10 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi {{^isReadOnly}} /** - * Set {{name}} - **/ - {{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} - {{/vendorExtensions.x-setter-extra-annotation}}public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + * Set {{name}} + */ +{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{> jackson_annotations}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { this.{{name}} = {{name}}; } @@ -135,14 +130,20 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + } this.{{name}}.add({{name}}Item); return this; } {{/isArray}} {{#isMap}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + } this.{{name}}.put(key, {{name}}Item); return this; } @@ -150,28 +151,6 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi {{/isReadOnly}} {{/vars}} +{{>pojoOverrides}} - /** - * Create a string representation of this pojo. - **/ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class {{classname}} {\n"); - {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} - {{#vars}}sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); - {{/vars}}sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private static String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojoOverrides.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojoOverrides.mustache new file mode 100644 index 000000000..12cc98045 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pojoOverrides.mustache @@ -0,0 +1,70 @@ + @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}} + + /** + * Create a string representation of this pojo. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} + {{#vars}} + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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 static String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom.mustache index d4199a18b..4a9b2f1f6 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom.mustache @@ -12,7 +12,7 @@ src/main/java - org.jboss.jandex + io.smallrye jandex-maven-plugin ${jandex.maven.plugin.version} @@ -58,9 +58,9 @@ - - junit - junit + + org.junit.jupiter + junit-jupiter-api ${junit.version} test @@ -101,13 +101,8 @@ ${smallrye.config.version} test - {{^disableMultipart}} - - org.apache.cxf - cxf-rt-rs-extension-providers - ${cxf.rt.rs.extension.providers.version} - - {{/disableMultipart}} + + {{#jsonb}} jakarta.json.bind jakarta.json.bind-api @@ -133,6 +128,38 @@ jaxb-impl ${jaxb.impl.version} + {{/jsonb}} + {{#jackson}} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + {{#withXml}} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + + {{/withXml}} + {{/jackson}} jakarta.activation jakarta.activation-api @@ -156,6 +183,21 @@ ${jakarta.annotation.version} provided +{{#microprofileMutiny}} + + io.smallrye.reactive + mutiny + ${mutiny.version} + +{{/microprofileMutiny}} +{{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + +{{/useReflectionEqualsHashCode}} @@ -170,15 +212,19 @@ 1.8 ${java.version} ${java.version} + UTF-8 + 1.5.18 9.2.9.v20150224 - 4.13.2 - 1.2.10 + 5.10.2 + 1.5.19 {{#useBeanValidation}} - 2.0.2 + 3.0.2 {{/useBeanValidation}} - 3.2.7 - 2.9.7 + 2.17.1 +{{#jackson}} + 2.17.1 +{{/jackson}} 1.2.2 1.3.5 1.0.2 @@ -188,13 +234,17 @@ {{microprofileRestClientVersion}} 1.2.1 1.3.5 - 3.2.6 2.2.11 2.2.11 5.2.2.Final - 1.1.0 + 3.2.7 2.6 1.9.1 - UTF-8 +{{#microprofileMutiny}} + 1.10.0 +{{/microprofileMutiny}} +{{#useReflectionEqualsHashCode}} + 3.17.0 +{{/useReflectionEqualsHashCode}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom_3.0.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom_3.0.mustache index f2795bf32..b161e2260 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom_3.0.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/pom_3.0.mustache @@ -12,7 +12,7 @@ src/main/java - org.jboss.jandex + io.smallrye jandex-maven-plugin ${jandex.maven.plugin.version} @@ -58,9 +58,9 @@ - - junit - junit + + org.junit.jupiter + junit-jupiter-api ${junit.version} test @@ -101,13 +101,8 @@ test - {{^disableMultipart}} - - org.apache.cxf - cxf-rt-rs-extension-providers - ${cxf.rt.rs.extension.providers.version} - - {{/disableMultipart}} + + {{#jsonb}} jakarta.json.bind jakarta.json.bind-api @@ -133,6 +128,38 @@ jaxb-impl ${jaxb.impl.version} + {{/jsonb}} + {{#jackson}} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + {{#withXml}} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + + {{/withXml}} + {{/jackson}} jakarta.activation jakarta.activation-api @@ -145,7 +172,7 @@ {{#useBeanValidationFeature}} - org.hibernate + org.hibernate.validator hibernate-validator ${hibernate.validator.version} @@ -156,6 +183,21 @@ ${jakarta.annotation.version} provided +{{#microprofileMutiny}} + + io.smallrye.reactive + mutiny + ${mutiny.version} + +{{/microprofileMutiny}} +{{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + +{{/useReflectionEqualsHashCode}} @@ -170,18 +212,23 @@ 11 ${java.version} ${java.version} + UTF-8 + 1.5.18 9.2.9.v20150224 - 4.13.2 - 1.2.10 + 5.10.2 + 1.5.19 {{#useBeanValidation}} 3.0.1 {{/useBeanValidation}} 3.2.7 - 2.13.2 + 2.17.1 +{{#jackson}} + 2.17.1 +{{/jackson}} 2.1.0 2.0.0 - 2.0.0 + 3.0.0 2.0.1 3.0.0 3.0.1 @@ -191,10 +238,15 @@ 3.5.1 3.0.2 3.0.2 - 7.0.4.Final - 1.1.0 + 8.0.3.Final + 3.2.7 2.6 1.9.1 - UTF-8 +{{#microprofileMutiny}} + 1.10.0 +{{/microprofileMutiny}} +{{#useReflectionEqualsHashCode}} + 3.17.0 +{{/useReflectionEqualsHashCode}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/server_operation.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/server_operation.mustache new file mode 100644 index 000000000..d6fa9054d --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/microprofile/server_operation.mustache @@ -0,0 +1 @@ +{{#vendorExtensions.x-multiple-2xx-response-operation}}{{#microprofileMutiny}}Uni{{/microprofileMutiny}}{{^microprofileMutiny}}Response{{/microprofileMutiny}}{{/vendorExtensions.x-multiple-2xx-response-operation}}{{^vendorExtensions.x-multiple-2xx-response-operation}}{{#microprofileMutiny}}Uni<{{{returnType}}}>{{/microprofileMutiny}}{{^microprofileMutiny}}{{{returnType}}}{{/microprofileMutiny}}{{/vendorExtensions.x-multiple-2xx-response-operation}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/AbstractOpenApiSchema.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/AbstractOpenApiSchema.mustache index d3b428159..8369ee728 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/AbstractOpenApiSchema.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/AbstractOpenApiSchema.mustache @@ -11,7 +11,8 @@ import com.fasterxml.jackson.annotation.JsonValue; /** * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}} +{{>generatedAnnotation}} + public abstract class AbstractOpenApiSchema { // store the actual instance of the schema/object @@ -133,4 +134,5 @@ public abstract class AbstractOpenApiSchema { {{>libraries/native/additional_properties}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiClient.mustache index 14add046a..0fd6071d3 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiClient.mustache @@ -11,6 +11,10 @@ import org.openapitools.jackson.nullable.JsonNullableModule; {{/openApiNullable}} import java.io.InputStream; +import java.io.IOException; +{{#useGzipFeature}} +import java.io.ByteArrayOutputStream; +{{/useGzipFeature}} import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; @@ -25,7 +29,17 @@ import java.util.Collections; import java.util.List; import java.util.StringJoiner; import java.util.function.Consumer; +import java.util.Optional; +import java.util.zip.GZIPInputStream; +{{#useGzipFeature}} +import java.util.function.Supplier; +import java.util.Objects; +import java.util.zip.GZIPOutputStream; +{{/useGzipFeature}} import java.util.stream.Collectors; +{{#useUnaryInterceptor}} +import java.util.function.UnaryOperator; +{{/useUnaryInterceptor}} import static java.nio.charset.StandardCharsets.UTF_8; @@ -43,21 +57,28 @@ import static java.nio.charset.StandardCharsets.UTF_8; * 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) { + protected HttpClient.Builder builder; + protected ObjectMapper mapper; + protected String scheme; + protected String host; + protected int port; + protected String basePath; + protected Consumer interceptor; +{{#useUnaryInterceptor}} + protected UnaryOperator> responseInterceptor; + protected UnaryOperator> asyncResponseInterceptor; +{{/useUnaryInterceptor}} +{{^useUnaryInterceptor}} + protected Consumer> responseInterceptor; + protected Consumer> asyncResponseInterceptor; +{{/useUnaryInterceptor}} + protected Duration readTimeout; + protected Duration connectTimeout; + + public static String valueToString(Object value) { if (value == null) { return ""; } @@ -157,7 +178,7 @@ public class ApiClient { public ApiClient() { this.builder = createDefaultHttpClientBuilder(); this.mapper = createDefaultObjectMapper(); - updateBaseUri(getDefaultBaseUri()); + updateBaseUri("{{{basePath}}}"); interceptor = null; readTimeout = null; connectTimeout = null; @@ -175,7 +196,7 @@ public class ApiClient { public ApiClient(HttpClient.Builder builder, ObjectMapper mapper, String baseUri) { this.builder = builder; this.mapper = mapper; - updateBaseUri(baseUri != null ? baseUri : getDefaultBaseUri()); + updateBaseUri(baseUri != null ? baseUri : "{{{basePath}}}"); interceptor = null; readTimeout = null; connectTimeout = null; @@ -183,7 +204,7 @@ public class ApiClient { asyncResponseInterceptor = null; } - protected ObjectMapper createDefaultObjectMapper() { + public static ObjectMapper createDefaultObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -196,18 +217,19 @@ public class ApiClient { {{#openApiNullable}} mapper.registerModule(new JsonNullableModule()); {{/openApiNullable}} + mapper.registerModule(new RFC3339JavaTimeModule()); return mapper; } - protected String getDefaultBaseUri() { - return "{{{basePath}}}"; + protected final String getDefaultBaseUri() { + return basePath; } - protected HttpClient.Builder createDefaultHttpClientBuilder() { + public static HttpClient.Builder createDefaultHttpClientBuilder() { return HttpClient.newBuilder(); } - public void updateBaseUri(String baseUri) { + public final void updateBaseUri(String baseUri) { URI uri = URI.create(baseUri); scheme = uri.getScheme(); host = uri.getHost(); @@ -348,12 +370,12 @@ public class ApiClient { * Set a custom response interceptor. * *

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

- * + *{{#useUnaryInterceptor}}

If you are using the UnaryInterceptor you can even manipulate the response to a certain degree

{{/useUnaryInterceptor}} * @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) { + public ApiClient setResponseInterceptor({{#useUnaryInterceptor}}UnaryOperator{{/useUnaryInterceptor}}{{^useUnaryInterceptor}}Consumer{{/useUnaryInterceptor}}> interceptor) { this.responseInterceptor = interceptor; return this; } @@ -363,7 +385,7 @@ public class ApiClient { * * @return The custom interceptor that was set, or null if there isn't any. */ - public Consumer> getResponseInterceptor() { + public {{#useUnaryInterceptor}}UnaryOperator{{/useUnaryInterceptor}}{{^useUnaryInterceptor}}Consumer{{/useUnaryInterceptor}}> getResponseInterceptor() { return responseInterceptor; } @@ -371,12 +393,12 @@ public class ApiClient { * 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

- * + *{{#useUnaryInterceptor}}

If you are using the UnaryInterceptor you can even manipulate the response to a certain degree

{{/useUnaryInterceptor}} * @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) { + public ApiClient setAsyncResponseInterceptor({{#useUnaryInterceptor}}UnaryOperator{{/useUnaryInterceptor}}{{^useUnaryInterceptor}}Consumer{{/useUnaryInterceptor}}> interceptor) { this.asyncResponseInterceptor = interceptor; return this; } @@ -386,7 +408,7 @@ public class ApiClient { * * @return The custom interceptor that was set, or null if there isn't any. */ - public Consumer> getAsyncResponseInterceptor() { + public {{#useUnaryInterceptor}}UnaryOperator{{/useUnaryInterceptor}}{{^useUnaryInterceptor}}Consumer{{/useUnaryInterceptor}}> getAsyncResponseInterceptor() { return asyncResponseInterceptor; } @@ -446,4 +468,145 @@ public class ApiClient { public Duration getConnectTimeout() { return connectTimeout; } + + /** + * Returns the response body InputStream, transparently decoding gzip-compressed + * payloads when the server sets {@code Content-Encoding: gzip}. + * + * @param response HTTP response whose body should be consumed + * @return Original or decompressed InputStream for the response body + * @throws IOException if the response body cannot be accessed or wrapping fails + */ + public static InputStream getResponseBody(HttpResponse response) throws IOException { + if (response == null) { + return null; + } + InputStream body = response.body(); + if (body == null) { + return null; + } + Optional encoding = response.headers().firstValue("Content-Encoding"); + if (encoding.isPresent()) { + for (String token : encoding.get().split(",")) { + if ("gzip".equalsIgnoreCase(token.trim())) { + return new GZIPInputStream(body, 8192); + } + } + } + return body; + } + +{{#useGzipFeature}} + /** + * Wraps a request body supplier with a streaming GZIP compressor so large payloads + * can be sent without buffering the entire contents in memory. + * + * @param bodySupplier Supplies the original request body InputStream + * @return BodyPublisher that emits gzip-compressed bytes from the supplied stream + */ + public static HttpRequest.BodyPublisher gzipRequestBody(Supplier bodySupplier) { + Objects.requireNonNull(bodySupplier, "bodySupplier must not be null"); + return HttpRequest.BodyPublishers.ofInputStream(() -> new GzipCompressingInputStream(bodySupplier)); + } + + private static final class GzipCompressingInputStream extends InputStream { + private final Supplier supplier; + private final byte[] readBuffer = new byte[8192]; + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private InputStream source; + private GZIPOutputStream gzipStream; + private byte[] currentChunk = new byte[0]; + private int chunkPosition = 0; + private boolean finished = false; + + private GzipCompressingInputStream(Supplier supplier) { + this.supplier = Objects.requireNonNull(supplier, "bodySupplier must not be null"); + } + + private void ensureInitialized() throws IOException { + if (source == null) { + source = Objects.requireNonNull(supplier.get(), "bodySupplier returned null InputStream"); + gzipStream = new GZIPOutputStream(buffer, true); + } + } + + private boolean fillBuffer() throws IOException { + while (chunkPosition >= currentChunk.length) { + buffer.reset(); + ensureInitialized(); + if (finished) { + return false; + } + int bytesRead = source.read(readBuffer); + if (bytesRead == -1) { + gzipStream.finish(); + gzipStream.close(); + source.close(); + finished = true; + } else { + gzipStream.write(readBuffer, 0, bytesRead); + gzipStream.flush(); + } + currentChunk = buffer.toByteArray(); + chunkPosition = 0; + if (currentChunk.length == 0 && !finished) { + continue; + } + if (currentChunk.length == 0 && finished) { + return false; + } + return true; + } + return true; + } + + @Override + public int read() throws IOException { + if (!fillBuffer()) { + return -1; + } + return currentChunk[chunkPosition++] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + if (!fillBuffer()) { + return -1; + } + int bytesToCopy = Math.min(len, currentChunk.length - chunkPosition); + System.arraycopy(currentChunk, chunkPosition, b, off, bytesToCopy); + chunkPosition += bytesToCopy; + return bytesToCopy; + } + + @Override + public void close() throws IOException { + IOException exception = null; + if (source != null) { + try { + source.close(); + } catch (IOException e) { + exception = e; + } finally { + source = null; + } + } + if (gzipStream != null) { + try { + gzipStream.close(); + } catch (IOException e) { + exception = exception == null ? e : exception; + } finally { + gzipStream = null; + } + } + if (exception != null) { + throw exception; + } + } + } +{{/useGzipFeature}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiResponse.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiResponse.mustache index 1e277319a..4d9f25f35 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiResponse.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/ApiResponse.mustache @@ -14,6 +14,8 @@ import java.util.TreeMap; * * @param The type of data that is deserialized from response body */ +{{>generatedAnnotation}} + public class ApiResponse { final private int statusCode; final private Map> headers; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/JSON.mustache index a13bf2fdf..a20207325 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/JSON.mustache @@ -1,7 +1,10 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; {{#openApiNullable}} import org.openapitools.jackson.nullable.JsonNullableModule; {{/openApiNullable}} @@ -20,20 +23,27 @@ 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()); + mapper = JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS) + {{#failOnUnknownProperties}} + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + {{/failOnUnknownProperties}} + {{^failOnUnknownProperties}} + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + {{/failOnUnknownProperties}} + .enable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .defaultDateFormat(new RFC3339DateFormat()) + .addModule(new JavaTimeModule()) + .build(); {{#joda}} mapper.registerModule(new JodaModule()); {{/joda}} @@ -79,6 +89,7 @@ public class JSON { /** * Helper class to register the discriminator mappings. */ + {{>generatedAnnotation}}{{! prevent indent}} private static class ClassDiscriminatorMapping { // The model class name. Class modelClass; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/README.mustache index ef15f379a..fd7d4905a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/README.mustache @@ -8,6 +8,8 @@ - Build date: {{generatedDate}} {{/hideGenerationTimestamp}} +- Generator version: {{generatorVersion}} + {{{appDescriptionWithNewLines}}} {{#infoUrl}} @@ -139,11 +141,14 @@ Class | Method | HTTP request | Description {{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) {{/model}}{{/models}} + ## Documentation for Authorization -{{^authMethods}}All endpoints do not require authorization. -{{/authMethods}}Authentication schemes defined for the API: -{{#authMethods}}### {{name}} +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{name}} {{#isApiKey}} @@ -151,10 +156,18 @@ Class | Method | HTTP request | Description - **API key parameter name**: {{keyParamName}} - **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} {{/isApiKey}} -{{#isBasic}} +{{#isBasicBasic}} - **Type**: HTTP basic authentication -{{/isBasic}} +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} {{#isOAuth}} - **Type**: OAuth diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/anyof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/anyof_model.mustache index c87c932fe..64c4dab03 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/anyof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/anyof_model.mustache @@ -16,12 +16,13 @@ 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}}.ApiClient; 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -54,7 +55,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im Object deserialized = null; {{#discriminator}} - Class cls = JSON.getClassForElement(tree, {{classname}}.class); + Class cls = JSON.getClassForElement(tree, new {{classname}}().getClass()); if (cls != null) { // When the OAS schema includes a discriminator, use the discriminator value to // discriminate the anyOf schemas. @@ -78,7 +79,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/anyOf}} - throw new IOException(String.format("Failed deserialization for {{classname}}: no match found")); + throw new IOException("Failed deserialization for {{classname}}: no match found"); } /** @@ -102,6 +103,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im 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. @@ -195,4 +197,168 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/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}} + {{^vendorExtensions.x-duplicated-data-type}} + if (getActualInstance() instanceof {{{dataType}}}) { + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(_item)))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(getActualInstance().get(i))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(_item)))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if (getActualInstance().get(i) != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf((({{{dataType}}})getActualInstance()).get(i))))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix), + getActualInstance().get(_key), ApiClient.urlEncode(String.valueOf((({{{dataType}}})getActualInstance()).get(_key))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isPrimitiveType}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if (getActualInstance() != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(String.valueOf(getActualInstance())))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if (getActualInstance() != null) { + joiner.add((({{{dataType}}})getActualInstance()).toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if (getActualInstance() != null) { + joiner.add(String.format(Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(String.valueOf(getActualInstance())))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + return joiner.toString(); + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.oneOf}} + return null; + } +{{/supportUrlQuery}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/api.mustache index 372ea7714..b65b8f395 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/api.mustache @@ -4,6 +4,7 @@ package {{package}}; import {{invokerPackage}}.ApiClient; import {{invokerPackage}}.ApiException; import {{invokerPackage}}.ApiResponse; +import {{invokerPackage}}.Configuration; import {{invokerPackage}}.Pair; {{#imports}} @@ -13,40 +14,91 @@ import {{import}}; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} +{{#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; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.StringJoiner; import java.util.List; import java.util.Map; import java.util.Set; +{{#useUnaryInterceptor}} +import java.util.function.UnaryOperator; +{{/useUnaryInterceptor}} import java.util.function.Consumer; -{{/fullJavaUtil}} +{{#useGzipFeature}} +import java.util.function.Supplier; +{{/useGzipFeature}} {{#asyncNative}} import java.util.concurrent.CompletableFuture; {{/asyncNative}} {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { + /** + * Utility class for extending HttpRequest.Builder functionality. + */ + private static class HttpRequestBuilderExtensions { + /** + * Adds additional headers to the provided HttpRequest.Builder. Useful for adding method/endpoint specific headers. + * + * @param builder the HttpRequest.Builder to which headers will be added + * @param headers a map of header names and values to add; may be null + * @return the same HttpRequest.Builder instance with the additional headers set + */ + static HttpRequest.Builder withAdditionalHeaders(HttpRequest.Builder builder, Map headers) { + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + } + return builder; + } + } private final HttpClient memberVarHttpClient; private final ObjectMapper memberVarObjectMapper; private final String memberVarBaseUri; - private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer memberVarInterceptor; + private final Consumer memberVarInterceptor; private final Duration memberVarReadTimeout; - private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer> memberVarResponseInterceptor; - private final {{#fullJavaUtil}}java.util.function.{{/fullJavaUtil}}Consumer> memberVarAsyncResponseInterceptor; +{{#useUnaryInterceptor}} + private final UnaryOperator> memberVarResponseInterceptor; + private final UnaryOperator> memberVarAsyncResponseInterceptor; +{{/useUnaryInterceptor}} +{{^useUnaryInterceptor}} + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; +{{/useUnaryInterceptor}} public {{classname}}() { - this(new ApiClient()); + this(Configuration.getDefaultApiClient()); } public {{classname}}(ApiClient apiClient) { @@ -58,17 +110,36 @@ public class {{classname}} { 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()); + private ApiException getApiException(String operationId, HttpResponse response) { + try { + InputStream responseBody = ApiClient.getResponseBody(response); + String body = null; + if (responseBody != null) { + body = new String(responseBody.readAllBytes()); + responseBody.close(); + } + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } catch (IOException e) { + return new ApiException(e); + } } {{/asyncNative}} {{^asyncNative}} protected ApiException getApiException(String operationId, HttpResponse response) throws IOException { - String body = response.body() == null ? null : new String(response.body().readAllBytes()); + InputStream responseBody = ApiClient.getResponseBody(response); + String body = null; + try { + body = responseBody == null ? null : new String(responseBody.readAllBytes()); + } finally { + if (responseBody != null) { + responseBody.close(); + } + } String message = formatExceptionMessage(operationId, response.statusCode(), body); return new ApiException(response.statusCode(), message, response.headers(), body); } @@ -81,13 +152,64 @@ public class {{classname}} { return operationId + " call failed with: " + statusCode + " - " + body; } + /** + * Download file from the given response. + * + * @param response Response + * @return File + * @throws ApiException If fail to read file content from response and write to disk + */ + public File downloadFileFromResponse(HttpResponse response, InputStream responseBody) throws ApiException { + if (responseBody == null) { + throw new ApiException(new IOException("Response body is empty")); + } + try { + File file = prepareDownloadFile(response); + java.nio.file.Files.copy(responseBody, file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + *

Prepare the file for download from the response.

+ * + * @param response a {@link java.net.http.HttpResponse} object. + * @return a {@link java.io.File} object. + * @throws java.io.IOException if any. + */ + private File prepareDownloadFile(HttpResponse response) throws IOException { + String filename = null; + java.util.Optional contentDisposition = response.headers().firstValue("Content-Disposition"); + if (contentDisposition.isPresent() && !"".equals(contentDisposition.get())) { + // Get filename from the Content-Disposition header. + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + java.util.regex.Matcher matcher = pattern.matcher(contentDisposition.get()); + if (matcher.find()) + filename = matcher.group(1); + } + File file = null; + if (filename != null) { + java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("swagger-gen-native"); + java.nio.file.Path filePath = java.nio.file.Files.createFile(tempDir.resolve(filename)); + file = filePath.toFile(); + tempDir.toFile().deleteOnExit(); // best effort cleanup + file.deleteOnExit(); // best effort cleanup + } else { + file = java.nio.file.Files.createTempFile("download-", "").toFile(); + file.deleteOnExit(); // best effort cleanup + } + return file; + } + {{#operation}} {{#vendorExtensions.x-group-parameters}} {{#hasParams}} /** * {{summary}} * {{notes}} - * @param apiRequest {@link API{{operationId}}Request} + * @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request} {{#returnType}} * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} {{/returnType}} @@ -108,17 +230,47 @@ public class {{classname}} { {{#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 { + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}(API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest) throws ApiException { + {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}(apiRequest, null); + } + + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request} + * @param headers Optional headers to include in the 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{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest, Map headers) throws ApiException { {{#allParams}} + {{>nullable_var_annotations}}{{! prevent indent}} {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); {{/allParams}} - {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers); } /** * {{summary}} * {{notes}} - * @param apiRequest {@link API{{operationId}}Request} + * @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request} * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} * @throws ApiException if fails to make API call {{#isDeprecated}} @@ -132,11 +284,33 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{operationId}}Request apiRequest) throws ApiException { + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest) throws ApiException { + return {{operationId}}WithHttpInfo(apiRequest, null); + } + + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request} + * @param headers Optional headers to include in the 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{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest, Map headers) throws ApiException { {{#allParams}} {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); {{/allParams}} - return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers); } {{/hasParams}} @@ -145,7 +319,7 @@ public class {{classname}} { * {{summary}} * {{notes}} {{#allParams}} - * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} {{/allParams}} {{#returnType}} * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} @@ -167,36 +341,48 @@ public class {{classname}} { {{#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 { + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}null); + } + + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} + {{/allParams}} + * @param headers Optional headers to include in the 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}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Map headers) throws ApiException { {{^asyncNative}} - {{#returnType}}ApiResponse<{{{.}}}> localVarResponse = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{#returnType}}ApiResponse<{{{.}}}> localVarResponse = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers); {{#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}} - }); + return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers) + .thenApply(ApiResponse::getData); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -208,8 +394,32 @@ public class {{classname}} { * {{summary}} * {{notes}} {{#allParams}} - * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + * @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}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}null); + } + + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}} {{/allParams}} + * @param headers Optional headers to include in the request * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} * @throws ApiException if fails to make API call {{#isDeprecated}} @@ -223,16 +433,22 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Map headers) throws ApiException { {{^asyncNative}} - HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers); try { HttpResponse localVarResponse = memberVarHttpClient.send( localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); if (memberVarResponseInterceptor != null) { + {{#useUnaryInterceptor}} + localVarResponse = memberVarResponseInterceptor.apply(localVarResponse); + {{/useUnaryInterceptor}} + {{^useUnaryInterceptor}} memberVarResponseInterceptor.accept(localVarResponse); + {{/useUnaryInterceptor}} } + InputStream localVarResponseBody = null; try { if (localVarResponse.statusCode()/ 100 != 2) { throw getApiException("{{operationId}}", localVarResponse); @@ -240,8 +456,9 @@ public class {{classname}} { {{#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))) { - java.util.Scanner s = new java.util.Scanner(localVarResponse.body()).useDelimiter("\\A"); + "text/plain".equalsIgnoreCase(localVarResponse.headers().map().get("Content-Type").get(0).split(";")[0].trim())) { + localVarResponseBody = ApiClient.getResponseBody(localVarResponse); + java.util.Scanner s = new java.util.Scanner(localVarResponseBody == null ? InputStream.nullInputStream() : localVarResponseBody).useDelimiter("\\A"); String responseBodyText = s.hasNext() ? s.next() : ""; return new ApiResponse( localVarResponse.statusCode(), @@ -253,25 +470,59 @@ public class {{classname}} { } {{/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}} + {{#returnType}} + {{! Fix for https://github.com/OpenAPITools/openapi-generator/issues/13968 }} + {{! This part had a bugfix for an empty response in the past, but this part of that PR was reverted because it was not doing anything. }} + {{! Keep this documentation here, because the problem is not obvious. }} + {{! `InputStream.available()` was used, but that only works for inputstreams that are already in memory, it will not give the right result if it is a remote stream. We only work with remote streams here. }} + {{! https://github.com/OpenAPITools/openapi-generator/pull/13993/commits/3e!37411d2acef0311c82e6d941a8e40b3bc0b6da }} + {{! The `available` method would work with a `PushbackInputStream`, because we could read 1 byte to check if it exists then push it back so Jackson can read it again. The issue with that is that it will also insert an ascii character for "head of input" and that will break Jackson as it does not handle special whitespace characters. }} + {{! A fix for that problem is to read it into a string and remove those characters, but if we need to read it before giving it to jackson to fix the string then just reading it into a string as is to do an emptiness check is the cleaner solution. }} + {{! We could also manipulate the inputstream to remove that bad character, but string manipulation is easier to read and this codepath is not asyncronus so we do not gain anything by reading the stream later. }} + {{! This fix does make it unsuitable for large amounts of data because `InputStream.readAllbytes` is not meant for it, but a synchronous client is already not the right tool for that.}} + localVarResponseBody = ApiClient.getResponseBody(localVarResponse); + if (localVarResponseBody == null) { + return new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + null + ); + } + + {{^isResponseFile}}{{#isResponseBinary}} + Byte[] responseValue = localVarResponseBody.readAllBytes(); + {{/isResponseBinary}}{{/isResponseFile}} + {{#isResponseFile}} + // Handle file downloading. + File responseValue = downloadFileFromResponse(localVarResponse, localVarResponseBody); + {{/isResponseFile}} + {{^isResponseBinary}}{{^isResponseFile}} + String responseBody = new String(localVarResponseBody.readAllBytes()); + {{{returnType}}} responseValue = responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {}); + {{/isResponseFile}}{{/isResponseBinary}} + + return new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseValue ); - {{/vendorExtensions.x-java-text-plain-string}} - } finally { + {{/returnType}} {{^returnType}} - // Drain the InputStream - while (localVarResponse.body().read() != -1) { - // Ignore + localVarResponseBody = ApiClient.getResponseBody(localVarResponse); + if (localVarResponseBody != null) { + localVarResponseBody.readAllBytes(); } - localVarResponse.body().close(); + return new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + null + ); {{/returnType}} + {{/vendorExtensions.x-java-text-plain-string}} + } finally { + if (localVarResponseBody != null) { + localVarResponseBody.close(); + } } } catch (IOException e) { throw new ApiException(e); @@ -283,34 +534,86 @@ public class {{classname}} { {{/asyncNative}} {{#asyncNative}} try { - HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers); return memberVarHttpClient.sendAsync( localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> { + HttpResponse.BodyHandlers.ofInputStream()).thenComposeAsync(localVarResponse -> { if (memberVarAsyncResponseInterceptor != null) { + {{#useUnaryInterceptor}} + localVarResponse = memberVarAsyncResponseInterceptor.apply(localVarResponse); + {{/useUnaryInterceptor}} + {{^useUnaryInterceptor}} memberVarAsyncResponseInterceptor.accept(localVarResponse); + {{/useUnaryInterceptor}} } 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}}}>() {})) - ); + InputStream localVarResponseBody = ApiClient.getResponseBody(localVarResponse); + try { + {{#vendorExtensions.x-java-text-plain-string}} + 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(localVarResponseBody == null ? InputStream.nullInputStream() : localVarResponseBody).useDelimiter("\\A"); + String responseBodyText = s.hasNext() ? s.next() : ""; + return CompletableFuture.completedFuture( + new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseBodyText + ) + ); + } else { + return CompletableFuture.failedFuture(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}} + {{#returnType}} + if (localVarResponseBody == null) { + return CompletableFuture.completedFuture( + new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + null + ) + ); + } + {{^isResponseFile}}{{#isResponseBinary}} + Byte[] responseValue = localVarResponseBody.readAllBytes(); + {{/isResponseBinary}}{{/isResponseFile}} + {{#isResponseFile}} + File responseValue = downloadFileFromResponse(localVarResponse, localVarResponseBody); + {{/isResponseFile}} + {{^isResponseBinary}}{{^isResponseFile}} + String responseBody = new String(localVarResponseBody.readAllBytes()); + {{{returnType}}} responseValue = responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {}); + {{/isResponseFile}}{{/isResponseBinary}} + return CompletableFuture.completedFuture( + new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseValue + ) + ); + {{/returnType}} + {{^returnType}} + if (localVarResponseBody != null) { + localVarResponseBody.readAllBytes(); + } + return CompletableFuture.completedFuture( + new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null) + ); + {{/returnType}} + {{/vendorExtensions.x-java-text-plain-string}} + } finally { + if (localVarResponseBody != null) { + localVarResponseBody.close(); + } + } } catch (IOException e) { return CompletableFuture.failedFuture(new ApiException(e)); } - {{/returnType}} - {{^returnType}} - return CompletableFuture.completedFuture( - new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null) - ); - {{/returnType}} } ); } @@ -320,7 +623,7 @@ public class {{classname}} { {{/asyncNative}} } - private HttpRequest.Builder {{operationId}}RequestBuilder({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + private HttpRequest.Builder {{operationId}}RequestBuilder({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Map headers) throws ApiException { {{#allParams}} {{#required}} // verify the required parameter '{{paramName}}' is set @@ -337,22 +640,28 @@ public class {{classname}} { .replace({{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, ApiClient.urlEncode({{{paramName}}}.toString())){{/pathParams}}; {{#hasQueryParams}} - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList<>(); + 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) { - {{#items.vars}} - {{#isArray}} - localVarQueryParams.addAll(ApiClient.parameterToPairs("csv", "{{paramName}}[{{name}}]", {{paramName}}.{{getter}}())); - {{/isArray}} - {{^isArray}} - localVarQueryParams.addAll(ApiClient.parameterToPairs("{{paramName}}[{{name}}]", {{paramName}}.{{getter}}())); - {{/isArray}} - {{/items.vars}} + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + localVarQueryStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format(java.util.Locale.ROOT, "{{baseName}}[%d]", i))); + } + {{/isArray}} + {{^isArray}} + String queryString = {{paramName}}.toUrlQueryString("{{baseName}}"); + if (!queryString.isBlank()) { + localVarQueryStringJoiner.add(queryString); + } + {{/isArray}} } {{/isDeepObject}} {{^isDeepObject}} @@ -368,7 +677,12 @@ public class {{classname}} { {{/vars}} {{/hasVars}} {{^hasVars}} + {{#isModel}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString()); + {{/isModel}} + {{^isModel}} localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isModel}} {{/hasVars}} {{/isExplode}} {{^isExplode}} @@ -378,9 +692,12 @@ public class {{classname}} { {{/collectionFormat}} {{/queryParams}} - if (!localVarQueryParams.isEmpty()) { - {{javaUtilPrefix}}StringJoiner queryJoiner = new {{javaUtilPrefix}}StringJoiner("&"); + 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)); @@ -396,46 +713,205 @@ public class {{classname}} { } {{/headerParams}} {{#bodyParam}} - localVarRequestBuilder.header("Content-Type", "{{#hasConsumes}}{{#consumes}}{{#-first}}{{mediaType}}{{/-first}}{{/consumes}}{{/hasConsumes}}{{#hasConsumes}}{{^consumes}}application/json{{/consumes}}{{/hasConsumes}}{{^hasConsumes}}application/json{{/hasConsumes}}"); + 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}}"); + localVarRequestBuilder.header("Accept", "{{#hasProduces}}{{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{/hasProduces}}{{#hasProduces}}{{^produces}}application/json{{/produces}}{{/hasProduces}}{{^hasProduces}}application/json{{/hasProduces}}"); + {{#useGzipFeature}} + localVarRequestBuilder.header("Accept-Encoding", "gzip"); + {{/useGzipFeature}} {{#bodyParam}} {{#isString}} + {{#useGzipFeature}} + Supplier localVarRequestBodySupplier = () -> new ByteArrayInputStream({{paramName}}.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + localVarRequestBuilder.header("Content-Encoding", "gzip"); + localVarRequestBuilder.method("{{httpMethod}}", ApiClient.gzipRequestBody(localVarRequestBodySupplier)); + {{/useGzipFeature}} + {{^useGzipFeature}} localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofString({{paramName}})); + {{/useGzipFeature}} {{/isString}} {{^isString}} try { byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes({{paramName}}); + {{#useGzipFeature}} + Supplier localVarRequestBodySupplier = () -> new ByteArrayInputStream(localVarPostBody); + localVarRequestBuilder.header("Content-Encoding", "gzip"); + localVarRequestBuilder.method("{{httpMethod}}", ApiClient.gzipRequestBody(localVarRequestBodySupplier)); + {{/useGzipFeature}} + {{^useGzipFeature}} localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody)); + {{/useGzipFeature}} } 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}} + if ({{paramName}}.get(i) != null) { + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.get(i).toString()); + } + {{/isFile}} + } + {{/isArray}} + {{^isArray}} + {{#isFile}} + multiPartBuilder.addBinaryBody("{{{baseName}}}", {{paramName}}); + hasFiles = true; + {{/isFile}} + {{^isFile}} + if ({{paramName}} != null) { + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.toString()); + } + {{/isFile}} + {{/isArray}} + {{/formParams}} + HttpEntity entity = multiPartBuilder.build(); + {{#useGzipFeature}} + Supplier formDataSupplier; + 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(); + formDataSupplier = () -> Channels.newInputStream(pipe.source()); + } else { + ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); + try { + entity.writeTo(formOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + byte[] formBytes = formOutputStream.toByteArray(); + formDataSupplier = () -> new ByteArrayInputStream(formBytes); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .header("Content-Encoding", "gzip") + .method("{{httpMethod}}", ApiClient.gzipRequestBody(formDataSupplier)); + {{/useGzipFeature}} + {{^useGzipFeature}} + 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); + } + byte[] formBytes = formOutputStream.toByteArray(); + formDataPublisher = HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formBytes)); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", formDataPublisher); + {{/useGzipFeature}} + {{/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); + } + byte[] formBytes = formOutputStream.toByteArray(); + {{#useGzipFeature}} + Supplier formDataSupplier = () -> new ByteArrayInputStream(formBytes); + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .header("Content-Encoding", "gzip") + .method("{{httpMethod}}", ApiClient.gzipRequestBody(formDataSupplier)); + {{/useGzipFeature}} + {{^useGzipFeature}} + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formBytes))); + {{/useGzipFeature}} + {{/isMultipart}} + {{/hasFormParams}} + {{^hasFormParams}} localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.noBody()); + {{/hasFormParams}} {{/bodyParam}} if (memberVarReadTimeout != null) { localVarRequestBuilder.timeout(memberVarReadTimeout); } + // Add custom headers if provided + localVarRequestBuilder = HttpRequestBuilderExtensions.withAdditionalHeaders(localVarRequestBuilder, headers); if (memberVarInterceptor != null) { memberVarInterceptor.accept(localVarRequestBuilder); } return localVarRequestBuilder; } + {{#vendorExtensions.x-group-parameters}} {{#hasParams}} - public static final class API{{operationId}}Request { + public static final class API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request { {{#requiredParams}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{dataType}}} {{paramName}}; // {{description}} (required) {{/requiredParams}} {{#optionalParams}} - private {{{dataType}}} {{paramName}}; // {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}} + {{>nullable_var_annotations}}{{! prevent indent}} + private {{{dataType}}} {{paramName}}; // {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}) {{/optionalParams}} - private API{{operationId}}Request(Builder builder) { + private API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request(Builder builder) { {{#requiredParams}} this.{{paramName}} = builder.{{paramName}}; {{/requiredParams}} @@ -444,6 +920,7 @@ public class {{classname}} { {{/optionalParams}} } {{#allParams}} + {{>nullable_var_annotations}}{{! prevent indent}} public {{{dataType}}} {{paramName}}() { return {{paramName}}; } @@ -461,13 +938,13 @@ public class {{classname}} { {{/optionalParams}} {{#allParams}} - public Builder {{paramName}}({{{dataType}}} {{paramName}}) { + public Builder {{paramName}}({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { this.{{paramName}} = {{paramName}}; return this; } {{/allParams}} - public API{{operationId}}Request build() { - return new API{{operationId}}Request(this); + public API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request build() { + return new API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request(this); } } } @@ -476,4 +953,4 @@ public class {{classname}} { {{/vendorExtensions.x-group-parameters}} {{/operation}} } -{{/operations}} +{{/operations}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/apiException.mustache index 3d0eee4bf..e8f7e3403 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/apiException.mustache @@ -5,7 +5,10 @@ package {{invokerPackage}}; import java.net.http.HttpHeaders; {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private HttpHeaders responseHeaders = null; private String responseBody = null; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/api_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/api_doc.mustache index 474cb7954..2b125e1f9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/api_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/api_doc.mustache @@ -266,7 +266,7 @@ public class Example { {{/responses.0}} {{#vendorExtensions.x-group-parameters}}{{#hasParams}} - + ## API{{operationId}}Request ### Properties {{#allParams}}{{#-last}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/api_test.mustache index ffcf05ec3..8558cc6f4 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/api_test.mustache @@ -5,25 +5,28 @@ package {{package}}; import {{invokerPackage}}.ApiException; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -{{/fullJavaUtil}} {{#asyncNative}} import java.util.concurrent.CompletableFuture; {{/asyncNative}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ -@Ignore +@Disabled public class {{classname}}Test { private final {{classname}} api = new {{classname}}(); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/build.gradle.mustache index a7ae19850..d632a8ab6 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/build.gradle.mustache @@ -1,5 +1,6 @@ apply plugin: 'idea' apply plugin: 'eclipse' +apply plugin: 'com.diffplug.spotless' group = '{{groupId}}' version = '{{artifactVersion}}' @@ -8,6 +9,9 @@ buildscript { repositories { mavenCentral() } + dependencies { + classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.11.0' + } } repositories { @@ -46,12 +50,12 @@ task execute(type:JavaExec) { } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -65,21 +69,72 @@ ext { {{#swagger1AnnotationLibrary}} swagger_annotations_version = "1.6.9" {{/swagger1AnnotationLibrary}} - jackson_version = "2.14.1" + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} + jackson_version = "2.19.2" + {{#useJakartaEe}} + jakarta_annotation_version = "2.1.1" + beanvalidation_version = "3.0.2" + {{/useJakartaEe}} + {{^useJakartaEe}} jakarta_annotation_version = "1.3.5" - junit_version = "4.13.2" + beanvalidation_version = "2.0.2" + {{/useJakartaEe}} + junit_version = "5.10.2" + {{#hasFormParamsInSpec}} + httpmime_version = "4.5.13" + {{/hasFormParamsInSpec}} + {{#useReflectionEqualsHashCode}} + commons_lang3_version = "3.17.0" + {{/useReflectionEqualsHashCode}} } 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 "org.openapitools:jackson-databind-nullable:0.2.9" implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$beanvalidation_version" + {{/useBeanValidation}} + {{#hasFormParamsInSpec}} + implementation "org.apache.httpcomponents:httpmime:$httpmime_version" + {{/hasFormParamsInSpec}} + {{#useReflectionEqualsHashCode}} + implementation "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/useReflectionEqualsHashCode}} + testImplementation "org.junit.jupiter:junit-jupiter-api:$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 { + // don't need to set target, it is inferred from java + // apply a specific flavor of google-java-format + googleJavaFormat('1.8').aosp().reflowLongStrings() + removeUnusedImports() + importOrder() + } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/generatedAnnotation.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/generatedAnnotation.mustache index 69d36472f..e05689d5f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/generatedAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/generatedAnnotation.mustache @@ -1 +1 @@ -@{{^useJakartaEe}}javax{{/useJakartaEe}}{{#useJakartaEe}}jakarta{{/useJakartaEe}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) +@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}, comments = "Generator version: {{generatorVersion}}") \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/model.mustache index 2611aac06..b3beca8d3 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/model.mustache @@ -16,8 +16,12 @@ 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}} @@ -36,23 +40,16 @@ import com.fasterxml.jackson.annotation.JsonCreator; {{/vendorExtensions.x-has-readonly-properties}} {{/jackson}} {{#withXml}} -import javax.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.*; {{/withXml}} {{#parcelableModel}} import android.os.Parcelable; import android.os.Parcel; {{/parcelableModel}} {{#useBeanValidation}} -{{^useJakartaEe}} -import javax.validation.constraints.*; -import javax.validation.Valid; -{{/useJakartaEe}} -{{#useJakartaEe}} -import jakarta.validation.constraints.*; -import jakarta.validation.Valid; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} - {{#performBeanValidation}} import org.hibernate.validator.constraints.*; {{/performBeanValidation}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/modelEnum.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/modelEnum.mustache new file mode 100644 index 000000000..ca351e003 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/modelEnum.mustache @@ -0,0 +1,123 @@ +{{#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}} +{{#isUri}} +import java.net.URI; +{{/isUri}} + +/** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ +{{#isDeprecated}} +@Deprecated +{{/isDeprecated}} +{{#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}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#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}} + } +{{#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(java.util.Locale.ROOT, "%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/boat-scaffold/src/main/templates/boat-java/libraries/native/model_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/model_doc.mustache index be1aedcf2..f17263f08 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/model_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/model_doc.mustache @@ -2,17 +2,21 @@ {{#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}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/oneof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/oneof_model.mustache index ae48daf75..c0f78805d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/oneof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/oneof_model.mustache @@ -18,12 +18,13 @@ 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}}.ApiClient; 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}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { @@ -67,7 +68,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im 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)); + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); } {{/discriminator}} @@ -111,7 +112,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im ret.setActualInstance(deserialized); return ret; } - throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1", match)); + throw new IOException(String.format(java.util.Locale.ROOT, "Failed deserialization for {{classname}}: %d classes match result, expected 1", match)); } /** @@ -135,6 +136,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im 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. @@ -228,4 +230,168 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/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}} + {{^vendorExtensions.x-duplicated-data-type}} + if (getActualInstance() instanceof {{{dataType}}}) { + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(_item)))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(getActualInstance().get(i))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf(_item)))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if (getActualInstance().get(i) != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(String.valueOf((({{{dataType}}})getActualInstance()).get(i))))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix), + getActualInstance().get(_key), ApiClient.urlEncode(String.valueOf((({{{dataType}}})getActualInstance()).get(_key))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isPrimitiveType}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if (getActualInstance() != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(String.valueOf(getActualInstance())))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if (getActualInstance() != null) { + joiner.add((({{{dataType}}})getActualInstance()).toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if (getActualInstance() != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(String.valueOf(getActualInstance())))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + return joiner.toString(); + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/composedSchemas.oneOf}} + return null; + } +{{/supportUrlQuery}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/pojo.mustache index e609be22a..abdf16d12 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/pojo.mustache @@ -1,6 +1,9 @@ {{#discriminator}} import {{invokerPackage}}.JSON; {{/discriminator}} +{{#supportUrlQuery}} +import {{invokerPackage}}.ApiClient; +{{/supportUrlQuery}} /** * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} * @deprecated{{/isDeprecated}} @@ -11,6 +14,11 @@ import {{invokerPackage}}.JSON; @ApiModel(description = "{{{.}}}") {{/description}} {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} {{#jackson}} @JsonPropertyOrder({ {{#vars}} @@ -32,11 +40,13 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{^isContainer}} {{^vendorExtensions.x-enum-as-string}} {{>modelInnerEnum}} + {{/vendorExtensions.x-enum-as-string}} {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} {{>modelInnerEnum}} + {{/mostInnerItems}} {{/isContainer}} {{/isEnum}} @@ -47,31 +57,16 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{/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}} @@ -82,12 +77,8 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{#isContainer}} - private {{{datatypeWithEnum}}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}}; - {{/isContainer}} - {{^isContainer}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/vars}} @@ -116,7 +107,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens )); {{/vendorExtensions.x-enum-as-string}} - public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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)); @@ -133,10 +124,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { {{#vendorExtensions.x-is-jackson-optional-nullable}} if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); } try { this.{{name}}.get().add({{name}}Item); @@ -146,11 +137,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.add({{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -158,10 +147,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isArray}} {{#isMap}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} put{{nameInPascalCase}}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}}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); } try { this.{{name}}.get().put(key, {{name}}Item); @@ -171,11 +160,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; } - {{/required}} this.{{name}}.put(key, {{name}}Item); return this; {{/vendorExtensions.x-is-jackson-optional-nullable}} @@ -183,7 +170,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/isMap}} {{/isReadOnly}} - /** + /** {{#description}} * {{.}} {{/description}} @@ -200,42 +187,21 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#deprecated}} * @deprecated {{/deprecated}} - **/ + */ {{#deprecated}} @Deprecated {{/deprecated}} -{{#required}} -{{#isNullable}} -{{^useJakartaEe}} - @javax.annotation.Nullable -{{/useJakartaEe}} -{{#useJakartaEe}} - @jakarta.annotation.Nullable -{{/useJakartaEe}} -{{/isNullable}} -{{^isNullable}} -{{^useJakartaEe}} - @javax.annotation.Nonnull -{{/useJakartaEe}} -{{#useJakartaEe}} - @jakarta.annotation.Nonnull -{{/useJakartaEe}} -{{/isNullable}} -{{/required}} -{{^required}} -{{^useJakartaEe}} - @javax.annotation.Nullable -{{/useJakartaEe}} -{{#useJakartaEe}} - @jakarta.annotation.Nullable -{{/useJakartaEe}} -{{/required}} + {{>nullable_var_annotations}}{{! prevent indent}} {{#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}} @@ -243,8 +209,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{!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}}{{#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) { @@ -260,6 +225,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#vendorExtensions.x-is-jackson-optional-nullable}} {{> jackson_annotations}} + public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { return {{name}}; } @@ -273,7 +239,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{^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-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullable_var_annotations}} {{{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)); @@ -291,6 +257,24 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{/vars}} {{>libraries/native/additional_properties}} + + {{#parent}} + {{#allVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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. */ @@ -344,7 +328,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens sb.append(" ").append(toIndentedString(super.toString())).append("\n"); {{/parent}} {{#vars}} - sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); {{/vars}} {{#additionalPropertiesType}} sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); @@ -363,7 +347,165 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } 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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(ApiClient.valueToString(_item)))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(ApiClient.valueToString({{getter}}().get(i))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(ApiClient.valueToString(_item)))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, i, containerSuffix), + ApiClient.urlEncode(ApiClient.valueToString({{getter}}().get(i))))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{^items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix), + {{getter}}().get(_key), ApiClient.urlEncode(ApiClient.valueToString({{getter}}().get(_key))))); + } + } + {{/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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(ApiClient.valueToString({{{getter}}}())))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if ({{getter}}() != null) { + joiner.add({{getter}}().toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if ({{getter}}() != null) { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{{baseName}}}%s=%s", prefix, suffix, ApiClient.urlEncode(ApiClient.valueToString({{{getter}}}())))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + {{/allVars}} + return joiner.toString(); + } +{{/supportUrlQuery}} {{#parcelableModel}} public void writeToParcel(Parcel out, int flags) { @@ -434,4 +576,8 @@ static { JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings); } {{/discriminator}} +{{#generateBuilders}} + + {{>javaBuilder}}{{! prevent indent}} +{{/generateBuilders}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/native/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/native/pom.mustache index 2a40aad08..b84cc87ee 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/native/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/native/pom.mustache @@ -64,7 +64,7 @@ maven-surefire-plugin - 3.0.0-M7 + 3.2.5 conf/log4j.properties @@ -134,6 +134,46 @@ + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + + + + .gitignore + + + + + + true + 4 + + + + + + + + + + 1.8 + + true + + + + + + @@ -168,6 +208,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -208,11 +255,35 @@ ${jakarta-annotation-version} provided + {{#useBeanValidation}} + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + + {{/useBeanValidation}} + {{#hasFormParamsInSpec}} + + org.apache.httpcomponents + httpmime + ${httpmime-version} + + {{/hasFormParamsInSpec}} + {{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + {{/useReflectionEqualsHashCode}} - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test @@ -223,11 +294,28 @@ {{#swagger1AnnotationLibrary}} 1.6.9 {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} 11 11 - 2.14.1 - 0.2.4 + 2.19.2 + 0.2.9 + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 - 4.13.2 + 2.0.2 + {{/useJakartaEe}} + {{#hasFormParamsInSpec}} + 4.5.14 + {{/hasFormParamsInSpec}} + {{#useReflectionEqualsHashCode}} + 3.17.0 + {{/useReflectionEqualsHashCode}} + 5.10.2 + 2.27.2 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/AbstractOpenApiSchema.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/AbstractOpenApiSchema.mustache index 3ba02e44c..30e31cf33 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/AbstractOpenApiSchema.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/AbstractOpenApiSchema.mustache @@ -6,14 +6,12 @@ import {{invokerPackage}}.ApiException; import java.util.Objects; import java.lang.reflect.Type; import java.util.Map; -import javax.ws.rs.core.GenericType; - -//import com.fasterxml.jackson.annotation.JsonValue; /** * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}} +{{>generatedAnnotation}} + public abstract class AbstractOpenApiSchema { // store the actual instance of the schema/object @@ -35,7 +33,7 @@ public abstract class AbstractOpenApiSchema { * * @return an instance of the actual schema/object */ - public abstract Map getSchemas(); + public abstract Map> getSchemas(); /** * Get the actual instance @@ -135,4 +133,5 @@ public abstract class AbstractOpenApiSchema { {{>libraries/jersey2/additional_properties}} + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/ApiClient.mustache index c81b9643a..499469238 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/ApiClient.mustache @@ -47,12 +47,15 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.DateFormat; +{{#jsr310}} import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +{{/jsr310}} import java.util.*; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -65,36 +68,67 @@ import {{invokerPackage}}.auth.OAuth; import {{invokerPackage}}.auth.RetryingOAuth; import {{invokerPackage}}.auth.OAuthFlow; {{/hasOAuthMethods}} +{{#withAWSV4Signature}} +import {{invokerPackage}}.auth.AWS4Auth; +{{/withAWSV4Signature}} /** *

ApiClient class.

*/ public class ApiClient { - private String basePath = "{{{basePath}}}"; - private boolean debugging = false; - private Map defaultHeaderMap = new HashMap(); - private Map defaultCookieMap = new HashMap(); - private String tempFolderPath = null; - - private Map authentications; - - private DateFormat dateFormat; - private DateFormat datetimeFormat; - private boolean lenientDatetimeFormat; - private int dateLength; - - private InputStream sslCaCert; - private boolean verifyingSsl; - private KeyManager[] keyManagers; - - private OkHttpClient httpClient; - private JSON json; - - private HttpLoggingInterceptor loggingInterceptor; + protected 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; + protected boolean debugging = false; + protected Map defaultHeaderMap = new HashMap(); + protected Map defaultCookieMap = new HashMap(); + protected String tempFolderPath = null; + + protected Map authentications; + + protected DateFormat dateFormat; + protected DateFormat datetimeFormat; + protected boolean lenientDatetimeFormat; + protected int dateLength; + + protected InputStream sslCaCert; + protected boolean verifyingSsl; + protected KeyManager[] keyManagers; + protected String tlsServerName; + + protected OkHttpClient httpClient; + protected JSON json; + + protected HttpLoggingInterceptor loggingInterceptor; {{#dynamicOperations}} - private Map operationLookupMap = new HashMap<>(); + protected Map operationLookupMap = new HashMap<>(); {{/dynamicOperations}} /** @@ -105,10 +139,11 @@ public class ApiClient { initHttpClient(); // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} - authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} // Prevent the authentications from being modified. authentications = Collections.unmodifiableMap(authentications); } @@ -124,10 +159,11 @@ public class ApiClient { httpClient = client; // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} - authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} // Prevent the authentications from being modified. authentications = Collections.unmodifiableMap(authentications); } @@ -196,9 +232,10 @@ public class ApiClient { ); initHttpClient(Collections.singletonList(retryingOAuth)); // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} - authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{/authMethods}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} // Prevent the authentications from being modified. authentications = Collections.unmodifiableMap(authentications); @@ -207,11 +244,11 @@ public class ApiClient { {{/-first}} {{/oauthMethods}} {{/hasOAuthMethods}} - private void initHttpClient() { + protected void initHttpClient() { initHttpClient(Collections.emptyList()); } - private void initHttpClient(List interceptors) { + protected void initHttpClient(List interceptors) { OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.addNetworkInterceptor(getProgressInterceptor()); for (Interceptor interceptor: interceptors) { @@ -225,7 +262,7 @@ public class ApiClient { httpClient = builder.build(); } - private void init() { + protected void init() { verifyingSsl = true; json = new JSON(); @@ -253,11 +290,39 @@ public class ApiClient { /** * Set base path * - * @param basePath Base path of the URL (e.g {{{basePath}}} - * @return An instance of OkHttpClient + * @param basePath Base path of the URL (e.g {{{basePath}}}) + * @return An instance of ApiClient */ 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; } @@ -274,7 +339,7 @@ public class ApiClient { * Set HTTP client, which must never be null. * * @param newHttpClient An instance of OkHttpClient - * @return Api Client + * @return ApiClient * @throws java.lang.NullPointerException when newHttpClient is null */ public ApiClient setHttpClient(OkHttpClient newHttpClient) { @@ -369,6 +434,29 @@ public class ApiClient { return this; } + /** + * Get TLS server name for SNI (Server Name Indication). + * + * @return The TLS server name + */ + public String getTlsServerName() { + return tlsServerName; + } + + /** + * Set TLS server name for SNI (Server Name Indication). + * This is used to verify the server certificate against a specific hostname + * instead of the hostname in the URL. + * + * @param tlsServerName The TLS server name to use for certificate verification + * @return ApiClient + */ + public ApiClient setTlsServerName(String tlsServerName) { + this.tlsServerName = tlsServerName; + applySslSettings(); + return this; + } + /** *

Getter for the field dateFormat.

* @@ -467,14 +555,23 @@ public class ApiClient { } {{#hasHttpBearerMethods}} - /** - * Helper method to set access token for the first Bearer authentication. - * @param bearerToken Bearer token - */ + /** + * Helper method to set access token for the first Bearer authentication. + * @param bearerToken Bearer token + */ public void setBearerToken(String bearerToken) { + setBearerToken(() -> bearerToken); + } + + /** + * Helper method to set the supplier of access tokens for Bearer authentication. + * + * @param tokenSupplier The supplier of bearer tokens + */ + public void setBearerToken(Supplier tokenSupplier) { for (Authentication auth : authentications.values()) { if (auth instanceof HttpBearerAuth) { - ((HttpBearerAuth) auth).setBearerToken(bearerToken); + ((HttpBearerAuth) auth).setBearerToken(tokenSupplier); return; } } @@ -559,6 +656,51 @@ public class ApiClient { throw new RuntimeException("No OAuth2 authentication configured!"); } + /** + * Helper method to set credentials for AWSV4 Signature + * + * @param accessKey Access Key + * @param secretKey Secret Key + * @param region Region + * @param service Service to access to + */ + public void setAWS4Configuration(String accessKey, String secretKey, String region, String service) { + {{#withAWSV4Signature}} + for (Authentication auth : authentications.values()) { + if (auth instanceof AWS4Auth) { + ((AWS4Auth) auth).setCredentials(accessKey, secretKey); + ((AWS4Auth) auth).setRegion(region); + ((AWS4Auth) auth).setService(service); + return; + } + } + {{/withAWSV4Signature}} + throw new RuntimeException("No AWS4 authentication configured!"); + } + + /** + * Helper method to set credentials for AWSV4 Signature + * + * @param accessKey Access Key + * @param secretKey Secret Key + * @param sessionToken Session Token + * @param region Region + * @param service Service to access to + */ + public void setAWS4Configuration(String accessKey, String secretKey, String sessionToken, String region, String service) { + {{#withAWSV4Signature}} + for (Authentication auth : authentications.values()) { + if (auth instanceof AWS4Auth) { + ((AWS4Auth) auth).setCredentials(accessKey, secretKey, sessionToken); + ((AWS4Auth) auth).setRegion(region); + ((AWS4Auth) auth).setService(service); + return; + } + } + {{/withAWSV4Signature}} + throw new RuntimeException("No AWS4 authentication configured!"); + } + /** * Set the User-Agent header's value (by adding to the default header map). * @@ -791,7 +933,7 @@ public class ApiClient { * @param value The value of the parameter. * @return A list of {@code Pair} objects. */ - public List parameterToPairs(String collectionFormat, String name, Collection value) { + public List parameterToPairs(String collectionFormat, String name, Collection value) { List params = new ArrayList(); // preconditions @@ -836,7 +978,7 @@ public class ApiClient { List params = new ArrayList(); // preconditions - if (param == null || param.getName() == null || param.getName().isEmpty() || value == null) { + if (param == null || param.getName() == null || param.getName().isEmpty() || value == null || value.isEmpty()) { return params; } @@ -871,6 +1013,31 @@ public class ApiClient { } {{/dynamicOperations}} + /** + * Formats the specified free-form query parameters to a list of {@code Pair} objects. + * + * @param value The free-form query parameters. + * @return A list of {@code Pair} objects. + */ + public List freeFormParameterToPairs(Object value) { + List params = new ArrayList<>(); + + // preconditions + if (value == null || !(value instanceof Map )) { + return params; + } + + @SuppressWarnings("unchecked") + final Map valuesMap = (Map) value; + + for (Map.Entry entry : valuesMap.entrySet()) { + params.add(new Pair(entry.getKey(), parameterToString(entry.getValue()))); + } + + return params; + } + + /** * Formats the specified collection path parameter to a string value. * @@ -913,7 +1080,7 @@ public class ApiClient { * @return The sanitized filename */ public String sanitizeFilename(String filename) { - return filename.replaceAll(".*[/\\\\]", ""); + return filename.replaceFirst("^.*[/\\\\]", ""); } /** @@ -1023,17 +1190,8 @@ public class ApiClient { return (T) downloadFileFromResponse(response); } - String respBody; - try { - if (response.body() != null) - respBody = response.body().string(); - else - respBody = null; - } catch (IOException e) { - throw new ApiException(e); - } - - if (respBody == null || "".equals(respBody)) { + ResponseBody respBody = response.body(); + if (respBody == null) { return null; } @@ -1042,17 +1200,35 @@ public class ApiClient { // ensuring a default content type contentType = "application/json"; } - if (isJsonMime(contentType)) { - return JSON.deserialize(respBody, returnType); - } else if (returnType.equals(String.class)) { - // Expecting string, return the raw response body. - return (T) respBody; - } else { - throw new ApiException( + try { + if (isJsonMime(contentType)) { + if (returnType.equals(String.class)) { + String respBodyString = respBody.string(); + if (respBodyString.isEmpty()) { + return null; + } + // Use String-based deserialize for String return type with fallback + return JSON.deserialize(respBodyString, returnType); + } else { + // Use InputStream-based deserialize which supports responses > 2GB + return JSON.deserialize(respBody.byteStream(), returnType); + } + } else if (returnType.equals(String.class)) { + String respBodyString = respBody.string(); + if (respBodyString.isEmpty()) { + return null; + } + // Expecting string, return the raw response body. + return (T) respBodyString; + } else { + throw new ApiException( "Content type \"" + contentType + "\" is not supported for type: " + returnType, response.code(), response.headers().toMultimap(), - respBody); + response.body().string()); + } + } catch (IOException e) { + throw new ApiException(e); } } @@ -1334,21 +1510,20 @@ public class ApiClient { * @throws {{invokerPackage}}.ApiException If fail to serialize the request body object */ public Request buildRequest(String baseUrl, String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String[] authNames, ApiCallback callback) throws ApiException { - // aggregate queryParams (non-collection) and collectionQueryParams into allQueryParams - List allQueryParams = new ArrayList(queryParams); - allQueryParams.addAll(collectionQueryParams); - final String url = buildUrl(baseUrl, path, queryParams, collectionQueryParams); // prepare HTTP request body RequestBody reqBody; String contentType = headerParams.get("Content-Type"); - + String contentTypePure = contentType; + if (contentTypePure != null && contentTypePure.contains(";")) { + contentTypePure = contentType.substring(0, contentType.indexOf(";")); + } if (!HttpMethod.permitsRequestBody(method)) { reqBody = null; - } else if ("application/x-www-form-urlencoded".equals(contentType)) { + } else if ("application/x-www-form-urlencoded".equals(contentTypePure)) { reqBody = buildRequestBodyFormEncoding(formParams); - } else if ("multipart/form-data".equals(contentType)) { + } else if ("multipart/form-data".equals(contentTypePure)) { reqBody = buildRequestBodyMultipart(formParams); } else if (body == null) { if ("DELETE".equals(method)) { @@ -1362,10 +1537,12 @@ public class ApiClient { reqBody = serialize(body, contentType); } + List updatedQueryParams = new ArrayList<>(queryParams); + // update parameters with authentication settings - updateParamsForAuth(authNames, allQueryParams, headerParams, cookieParams, requestBodyToString(reqBody), method, URI.create(url)); + updateParamsForAuth(authNames, updatedQueryParams, headerParams, cookieParams, requestBodyToString(reqBody), method, URI.create(url)); - final Request.Builder reqBuilder = new Request.Builder().url(url); + final Request.Builder reqBuilder = new Request.Builder().url(buildUrl(baseUrl, path, updatedQueryParams, collectionQueryParams)); processHeaderParams(headerParams, reqBuilder); processCookieParams(cookieParams, reqBuilder); @@ -1399,7 +1576,19 @@ public class ApiClient { if (baseUrl != null) { url.append(baseUrl).append(path); } else { - url.append(basePath).append(path); + String baseURL; + if (serverIndex != null) { + if (serverIndex < 0 || serverIndex >= servers.size()) { + throw new ArrayIndexOutOfBoundsException(String.format( + java.util.Locale.ROOT, + "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; + } + url.append(baseURL).append(path); } if (queryParams != null && !queryParams.isEmpty()) { @@ -1464,11 +1653,11 @@ public class ApiClient { */ public void processCookieParams(Map cookieParams, Request.Builder reqBuilder) { for (Entry param : cookieParams.entrySet()) { - reqBuilder.addHeader("Cookie", String.format("%s=%s", param.getKey(), param.getValue())); + reqBuilder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", param.getKey(), param.getValue())); } for (Entry param : defaultCookieMap.entrySet()) { if (!cookieParams.containsKey(param.getKey())) { - reqBuilder.addHeader("Cookie", String.format("%s=%s", param.getKey(), param.getValue())); + reqBuilder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", param.getKey(), param.getValue())); } } } @@ -1561,7 +1750,7 @@ public class ApiClient { * @param key The key of the Header element * @param file The file to add to the Header */ - private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { + protected void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); mpBuilder.addPart(partHeaders, RequestBody.create(file, mediaType)); @@ -1574,7 +1763,7 @@ public class ApiClient { * @param key The key of the Header element * @param obj The complex object to add to the Header */ - private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, Object obj) { + protected void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, Object obj) { RequestBody requestBody; if (obj instanceof String) { requestBody = RequestBody.create((String) obj, MediaType.parse("text/plain")); @@ -1596,7 +1785,7 @@ public class ApiClient { * Get network interceptor to add it to the httpClient to track download progress for * async requests. */ - private Interceptor getProgressInterceptor() { + protected Interceptor getProgressInterceptor() { return new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { @@ -1617,7 +1806,7 @@ public class ApiClient { * Apply SSL related settings to httpClient according to the current values of * verifyingSsl and sslCaCert. */ - private void applySslSettings() { + protected void applySslSettings() { try { TrustManager[] trustManagers; HostnameVerifier hostnameVerifier; @@ -1665,7 +1854,17 @@ public class ApiClient { trustManagerFactory.init(caKeyStore); } trustManagers = trustManagerFactory.getTrustManagers(); - hostnameVerifier = OkHostnameVerifier.INSTANCE; + if (tlsServerName != null && !tlsServerName.isEmpty()) { + hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + // Verify the certificate against tlsServerName instead of the actual hostname + return OkHostnameVerifier.INSTANCE.verify(tlsServerName, session); + } + }; + } else { + hostnameVerifier = OkHostnameVerifier.INSTANCE; + } } SSLContext sslContext = SSLContext.getInstance("TLS"); @@ -1679,7 +1878,7 @@ public class ApiClient { } } - private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { + protected KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, password); @@ -1707,7 +1906,7 @@ public class ApiClient { return this; } - private void addOperationLookupEntry(String path, String method, Operation operation) { + protected void addOperationLookupEntry(String path, String method, Operation operation) { if ( operation != null && operation.getOperationId() != null) { operationLookupMap.put( operation.getOperationId(), @@ -1767,7 +1966,7 @@ public class ApiClient { * @return The string representation of the HTTP request body * @throws {{invokerPackage}}.ApiException If fail to serialize the request body object into a string */ - private String requestBodyToString(RequestBody requestBody) throws ApiException { + protected String requestBodyToString(RequestBody requestBody) throws ApiException { if (requestBody != null) { try { final Buffer buffer = new Buffer(); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/JSON.mustache index d6883b982..eeb26cb9c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/JSON.mustache @@ -23,14 +23,19 @@ import org.joda.time.format.ISODateTimeFormat; import okio.ByteString; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.StringReader; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.ParsePosition; +{{#jsr310}} import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +{{/jsr310}} import java.util.Date; import java.util.Locale; import java.util.Map; @@ -109,7 +114,7 @@ public class JSON { return clazz; } - { + static { GsonBuilder gsonBuilder = createGson(); gsonBuilder.registerTypeAdapter(Date.class, dateTypeAdapter); gsonBuilder.registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter); @@ -196,6 +201,28 @@ public class JSON { } } + /** + * Deserialize the given JSON InputStream to a Java object. + * + * @param Type + * @param inputStream The JSON InputStream + * @param returnType The type to deserialize into + * @return The deserialized Java object + */ + @SuppressWarnings("unchecked") + public static T deserialize(InputStream inputStream, Type returnType) throws IOException { + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + if (isLenientOnJson) { + // see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean) + JsonReader jsonReader = new JsonReader(reader); + jsonReader.setLenient(true); + return gson.fromJson(jsonReader, returnType); + } else { + return gson.fromJson(reader, returnType); + } + } + } + /** * Gson TypeAdapter for Byte Array type */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/README.mustache index b4b5d2cdd..de3afa6c1 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/README.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/README.mustache @@ -5,6 +5,7 @@ {{^hideGenerationTimestamp}} - Build date: {{generatedDate}} {{/hideGenerationTimestamp}} + - Generator version: {{generatorVersion}} {{{appDescriptionWithNewLines}}} @@ -89,13 +90,17 @@ import {{{invokerPackage}}}.ApiClient; import {{{invokerPackage}}}.ApiException; import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}} import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} -import {{{invokerPackage}}}.models.*; +import {{{modelPackage}}}.*; import {{{package}}}.{{{classname}}}; public class Example { public static void main(String[] args) { ApiClient defaultClient = Configuration.getDefaultApiClient(); defaultClient.setBasePath("{{{basePath}}}"); + {{#withAWSV4Signature}} + // Configure AWS Signature V4 authorization + defaultClient.setAWS4Configuration("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY", "REGION", "SERVICE") + {{/withAWSV4Signature}} {{#hasAuthMethods}} {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} // Configure HTTP basic authorization: {{{name}}} @@ -151,18 +156,25 @@ Class | Method | HTTP request | Description {{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) {{/model}}{{/models}} + ## Documentation for Authorization -{{^authMethods}}All endpoints do not require authorization. -{{/authMethods}}Authentication schemes defined for the API: -{{#authMethods}}### {{name}} +{{^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}} -{{#isBasic}}- **Type**: HTTP basic authentication -{{/isBasic}} +{{#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}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/anyof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/anyof_model.mustache index bdaddadae..c93c9b3f2 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/anyof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/anyof_model.mustache @@ -1,4 +1,4 @@ -import javax.ws.rs.core.GenericType; + import java.io.IOException; import java.lang.reflect.Type; @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.google.gson.Gson; @@ -27,12 +28,13 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonArray; import com.google.gson.JsonParseException; import {{invokerPackage}}.JSON; {{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}} -public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class CustomTypeAdapterFactory implements TypeAdapterFactory { @@ -43,9 +45,20 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return null; // this class only serializes '{{classname}}' and its subtypes } final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + {{#composedSchemas}} {{#anyOf}} - final TypeAdapter<{{.}}> adapter{{.}} = gson.getDelegateAdapter(this, TypeToken.get({{.}}.class)); + {{^isArray}} + {{^vendorExtensions.x-duplicated-data-type}} + final TypeAdapter<{{{dataType}}}> adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}.class)); + {{/vendorExtensions.x-duplicated-data-type}} + {{/isArray}} + {{#isArray}} + + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + {{/isArray}} {{/anyOf}} + {{/composedSchemas}} return (TypeAdapter) new TypeAdapter<{{classname}}>() { @Override @@ -55,86 +68,170 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return; } + {{#composedSchemas}} {{#anyOf}} - // check if the actual instance is of the type `{{.}}` - if (value.getActualInstance() instanceof {{.}}) { - JsonObject obj = adapter{{.}}.toJsonTree(({{.}})value.getActualInstance()).getAsJsonObject(); - elementAdapter.write(out, obj); + {{^vendorExtensions.x-duplicated-data-type}} + // check if the actual instance is of the type `{{{dataType}}}` + if (value.getActualInstance() instanceof {{#isArray}}List{{/isArray}}{{^isArray}}{{{dataType}}}{{/isArray}}) { + {{#isPrimitiveType}} + JsonPrimitive primitive = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); + elementAdapter.write(out, primitive); + return; + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + List list = (List) value.getActualInstance(); + if (!list.isEmpty() && list.get(0) instanceof {{{items.dataType}}}) { + JsonArray array = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); + elementAdapter.write(out, array); + return; + } + {{/isArray}} + {{/isPrimitiveType}} + {{^isArray}} + {{^isPrimitiveType}} + JsonElement element = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()); + elementAdapter.write(out, element); return; + {{/isPrimitiveType}} + {{/isArray}} } - + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} throw new IOException("Failed to serialize as the type doesn't match anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}"); } @Override public {{classname}} read(JsonReader in) throws IOException { Object deserialized = null; - JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); - - {{#useOneOfDiscriminatorLookup}} - {{#discriminator}} - // use discriminator value for faster anyOf lookup - {{classname}} new{{classname}} = new {{classname}}(); - String discriminatorValue = elementAdapter.read(in).getAsJsonObject().get("{{{propertyBaseName}}}").getAsString(); - switch (discriminatorValue) { - {{#mappedModels}} - case "{{{mappingName}}}": - deserialized = adapter{{modelName}}.fromJsonTree(jsonObject); - 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)); - } + JsonElement jsonElement = elementAdapter.read(in); + + ArrayList errorMessages = new ArrayList<>(); + TypeAdapter actualAdapter = elementAdapter; - {{/discriminator}} - {{/useOneOfDiscriminatorLookup}} + {{#composedSchemas}} {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{^hasVars}} + // deserialize {{{dataType}}} + try { + // validate the JSON object to see if any exception is thrown + {{^isArray}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#isUuid}} + UUID.fromString(jsonElement.getAsString()); + {{/isUuid}} + {{^isUuid}} + {{{dataType}}}.validateJsonElement(jsonElement); + {{/isUuid}} + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{/isArray}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{{dataType}}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isArray}} + {{classname}} ret = new {{classname}}(); + ret.setActualInstance(actualAdapter.fromJsonTree(jsonElement)); + return ret; + } catch (Exception e) { + // deserialization failed, continue + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + log.log(Level.FINER, "Input data does not match schema '{{{dataType}}}'", e); + } + {{/hasVars}} + {{#hasVars}} // deserialize {{{.}}} try { // validate the JSON object to see if any exception is thrown - {{.}}.validateJsonObject(jsonObject); - log.log(Level.FINER, "Input data matches schema '{{{.}}}'"); + {{.}}.validateJsonElement(jsonElement); + actualAdapter = adapter{{.}}; {{classname}} ret = new {{classname}}(); - ret.setActualInstance(adapter{{.}}.fromJsonTree(jsonObject)); + ret.setActualInstance(actualAdapter.fromJsonTree(jsonElement)); return ret; } catch (Exception e) { // deserialization failed, continue + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{.}}} failed with `%s`.", e.getMessage())); log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); } - + {{/hasVars}} + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} - throw new IOException(String.format("Failed deserialization for {{classname}}: no class matched. JSON: %s", jsonObject.toString())); + throw new IOException(String.format(java.util.Locale.ROOT, "Failed deserialization for {{classname}}: no class matches result, expected at least 1. Detailed failure message for anyOf schemas: %s. JSON: %s", errorMessages, jsonElement.toString())); } }.nullSafe(); } } // store a list of schema names defined in anyOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap>(); public {{classname}}() { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } - {{#anyOf}} - public {{classname}}({{{.}}} o) { + public {{classname}}(Object o) { super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } - {{/anyOf}} static { + {{#composedSchemas}} {{#anyOf}} - schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { - }); + {{^vendorExtensions.x-duplicated-data-type}} + schemas.put("{{{dataType}}}", {{{baseType}}}.class); + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -144,7 +241,6 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * {{#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) { @@ -155,13 +251,26 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#anyOf}} - if (instance instanceof {{{.}}}) { + {{^vendorExtensions.x-duplicated-data-type}} + if (instance instanceof {{#isArray}}List{{/isArray}}{{^isArray}}{{{dataType}}}{{/isArray}}) { + {{#isArray}} + List list = (List) instance; + if (!list.isEmpty() && list.get(0) instanceof {{{items.dataType}}}) { + super.setActualInstance(instance); + return; + } + {{/isArray}} + {{^isArray}} super.setActualInstance(instance); return; + {{/isArray}} } + {{/vendorExtensions.x-duplicated-data-type}} {{/anyOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}"); } @@ -171,66 +280,129 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * * @return The actual instance ({{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}) */ + @SuppressWarnings("unchecked") @Override public Object getActualInstance() { return super.getActualInstance(); } + {{#composedSchemas}} {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/anyOf}} + {{/composedSchemas}} + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to {{classname}} + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + // validate anyOf schemas one by one + ArrayList errorMessages = new ArrayList<>(); + {{#composedSchemas}} + {{#anyOf}} + {{^vendorExtensions.x-duplicated-data-type}} + // validate the json string with {{{dataType}}} + try { + {{^hasVars}} + {{^isArray}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#isUuid}} + UUID.fromString(jsonElement.getAsString()); + {{/isUuid}} + {{^isUuid}} + {{{dataType}}}.validateJsonElement(jsonElement); + {{/isUuid}} + {{/isPrimitiveType}} + {{/isNumber}} + {{/isArray}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{{dataType}}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isArray}} + {{/hasVars}} + {{#hasVars}} + {{{.}}}.validateJsonElement(jsonElement); + return; + {{/hasVars}} + return; + } catch (Exception e) { + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + // continue to the next one + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/anyOf}} + {{/composedSchemas}} + throw new IOException(String.format(java.util.Locale.ROOT, "The JSON string is invalid for {{classname}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. no class match the result, expected at least 1. Detailed failure message for anyOf schemas: %s. JSON: %s", errorMessages, jsonElement.toString())); + } - /** - * Validates the JSON Object and throws an exception if issues found - * - * @param jsonObj JSON Object - * @throws IOException if the JSON Object is invalid with respect to {{classname}} - */ - public static void validateJsonObject(JsonObject jsonObj) throws IOException { - // validate anyOf schemas one by one - int validCount = 0; - {{#anyOf}} - // validate the json string with {{{.}}} - try { - {{{.}}}.validateJsonObject(jsonObj); - return; // return earlier as at least one schema is valid with respect to the Json object - //validCount++; - } catch (Exception e) { - // continue to the next one + /** + * Create an instance of {{classname}} given an JSON string + * + * @param jsonString JSON string + * @return An instance of {{classname}} + * @throws IOException if the JSON string is invalid with respect to {{classname}} + */ + public static {{{classname}}} fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); } - {{/anyOf}} - if (validCount == 0) { - throw new IOException(String.format("The JSON string is invalid for {{classname}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. JSON: %s", jsonObj.toString())); + + /** + * Convert an instance of {{classname}} to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); } - } - - /** - * Create an instance of {{classname}} given an JSON string - * - * @param jsonString JSON string - * @return An instance of {{classname}} - * @throws IOException if the JSON string is invalid with respect to {{classname}} - */ - public static {{{classname}}} fromJson(String jsonString) throws IOException { - return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); - } - - /** - * Convert an instance of {{classname}} to an JSON string - * - * @return JSON string - */ - public String toJson() { - return JSON.getGson().toJson(this); - } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api.mustache index d39478dec..9519ce13c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api.mustache @@ -26,13 +26,14 @@ import io.swagger.v3.oas.models.parameters.Parameter; import java.io.IOException; {{#useBeanValidation}} -import javax.validation.constraints.*; +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} {{#performBeanValidation}} -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.ValidatorFactory; -import javax.validation.executable.ExecutableValidator; +import {{javaxPackage}}.validation.ConstraintViolation; +import {{javaxPackage}}.validation.Validation; +import {{javaxPackage}}.validation.ValidatorFactory; +import {{javaxPackage}}.validation.executable.ExecutableValidator; import java.util.Set; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -42,7 +43,6 @@ import java.lang.reflect.Type; {{/imports}} import java.lang.reflect.Type; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -50,8 +50,6 @@ import java.util.Map; {{#supportStreaming}} import java.io.InputStream; {{/supportStreaming}} -{{/fullJavaUtil}} -import javax.ws.rs.core.GenericType; {{#operations}} public class {{classname}} { @@ -100,7 +98,8 @@ public class {{classname}} { * @throws ApiException If fail to serialize the request body object {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -118,7 +117,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} okhttp3.Call {{operationId}}Call({{#allParams}}{{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback _callback) throws ApiException { + public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} okhttp3.Call {{operationId}}Call({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback _callback) throws ApiException { String basePath = null; // Operation Servers String[] localBasePaths = new String[] { {{#servers}}"{{{url}}}"{{^-last}}, {{/-last}}{{/servers}} }; @@ -156,11 +155,11 @@ public class {{classname}} { {{/allParams}} {{/dynamicOperations}} - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}List localVarCollectionQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); {{#formParams}} if ({{paramName}} != null) { @@ -171,22 +170,37 @@ public class {{classname}} { {{^dynamicOperations}} {{#queryParams}} if ({{paramName}} != null) { - {{#collectionFormat}}localVarCollectionQueryParams.addAll(localVarApiClient.parameterToPairs("{{{.}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(localVarApiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); + {{#isFreeFormObject}}localVarQueryParams.addAll(localVarApiClient.freeFormParameterToPairs({{paramName}}));{{/isFreeFormObject}}{{^isFreeFormObject}}{{#collectionFormat}}localVarCollectionQueryParams.addAll(localVarApiClient.parameterToPairs("{{{.}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(localVarApiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}}));{{/isFreeFormObject}} } {{/queryParams}} - {{#headerParams}} - if ({{paramName}} != null) { - localVarHeaderParams.put("{{baseName}}", localVarApiClient.parameterToString({{paramName}})); - } - - {{/headerParams}} + {{#constantParams}} + {{#isQueryParam}} + // Set client side default value of Query Param "{{baseName}}". + localVarCollectionQueryParams.add(new Pair("{{baseName}}", {{#_enum}}"{{{.}}}"{{/_enum}})); + + {{/isQueryParam}} + {{/constantParams}} + {{#constantParams}} + {{#isHeaderParam}} + // Set client side default value of Header Param "{{baseName}}". + localVarHeaderParams.put("{{baseName}}", {{#_enum}}"{{{.}}}"{{/_enum}}); + + {{/isHeaderParam}} + {{/constantParams}} {{#cookieParams}} if ({{paramName}} != null) { localVarCookieParams.put("{{baseName}}", localVarApiClient.parameterToString({{paramName}})); } {{/cookieParams}} + {{#constantParams}} + {{#isCookieParam}} + // Set client side default value of Cookie Param "{{baseName}}". + localVarCookieParams.put("{{baseName}}", {{#_enum}}"{{{.}}}"{{/_enum}}); + + {{/isCookieParam}} + {{/constantParams}} {{/dynamicOperations}} {{#dynamicOperations}} localVarPath = localVarApiClient.fillParametersFromOperation(operation, paramMap, localVarPath, localVarQueryParams, localVarCollectionQueryParams, localVarHeaderParams, localVarCookieParams); @@ -211,8 +225,17 @@ public class {{classname}} { if (localVarContentType != null) { localVarHeaderParams.put("Content-Type", localVarContentType); } + {{^dynamicOperations}} + {{#headerParams}} + + if ({{paramName}} != null) { + localVarHeaderParams.put("{{baseName}}", localVarApiClient.parameterToString({{paramName}})); + } + + {{/headerParams}} + {{/dynamicOperations}} - String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; + String[] localVarAuthNames = new String[] { {{#withAWSV4Signature}}"AWS4Auth"{{/withAWSV4Signature}}{{#authMethods}}{{#-first}}{{#withAWSV4Signature}}, {{/withAWSV4Signature}}{{/-first}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; return localVarApiClient.buildCall(basePath, localVarPath, {{^dynamicOperations}}"{{httpMethod}}"{{/dynamicOperations}}{{#dynamicOperations}}apiOperation.getMethod(){{/dynamicOperations}}, localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); } @@ -220,7 +243,7 @@ public class {{classname}} { @Deprecated {{/isDeprecated}} @SuppressWarnings("rawtypes") - private okhttp3.Call {{operationId}}ValidateBeforeCall({{#allParams}}{{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback _callback) throws ApiException { + private okhttp3.Call {{operationId}}ValidateBeforeCall({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback _callback) throws ApiException { {{^performBeanValidation}} {{#allParams}} {{#required}} @@ -240,7 +263,7 @@ public class {{classname}} { ExecutableValidator executableValidator = factory.getValidator().forExecutables(); Object[] parameterValues = { {{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}} }; - Method method = this.getClass().getMethod("{{operationId}}WithHttpInfo"{{#allParams}}, {{#isArray}}java.util.List{{/isArray}}{{#isMap}}java.util.Map{{/isMap}}{{^isArray}}{{^isMap}}{{{dataType}}}{{/isMap}}{{/isArray}}.class{{/allParams}}); + Method method = this.getClass().getMethod("{{operationId}}WithHttpInfo"{{#allParams}}, {{#isArray}}java.util.List{{/isArray}}{{#isMap}}java.util.Map{{/isMap}}{{^isArray}}{{^isMap}}{{>nullable_var_annotations}} {{{dataType}}}{{/isMap}}{{/isArray}}.class{{/allParams}}); Set> violations = executableValidator.validateParameters(this, method, parameterValues); @@ -268,7 +291,8 @@ public class {{classname}} { * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -287,13 +311,13 @@ public class {{classname}} { @Deprecated {{/isDeprecated}} {{#vendorExtensions.x-streaming}} - public {{#returnType}}InputStream {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}InputStream {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { {{#returnType}}InputStream localVarResp = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} return localVarResp;{{/returnType}} } {{/vendorExtensions.x-streaming}} {{^vendorExtensions.x-streaming}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { {{#returnType}}ApiResponse<{{{.}}}> localVarResp = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} return localVarResp.getData();{{/returnType}} } @@ -308,7 +332,8 @@ public class {{classname}} { * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -326,7 +351,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-streaming}} InputStream {{operationId}}WithHttpInfo({{#allParams}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-streaming}} InputStream {{operationId}}WithHttpInfo({{#allParams}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { okhttp3.Call localVarCall = {{operationId}}ValidateBeforeCall({{#allParams}}{{paramName}}, {{/allParams}}null); {{#returnType}} {{#errorObjectType}} @@ -344,7 +369,7 @@ public class {{classname}} { {{/errorObjectType}} {{/returnType}} } - {{/vendorExtensions.x-streaming}}{{^vendorExtensions.x-streaming}} ApiResponse<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{/vendorExtensions.x-streaming}}{{^vendorExtensions.x-streaming}} ApiResponse<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { okhttp3.Call localVarCall = {{operationId}}ValidateBeforeCall({{#allParams}}{{paramName}}, {{/allParams}}null); {{^returnType}} return localVarApiClient.execute(localVarCall); @@ -376,7 +401,8 @@ public class {{classname}} { * @throws ApiException If fail to process the API call, e.g. serializing the request body object {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -394,7 +420,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} okhttp3.Call {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback<{{{returnType}}}{{^returnType}}Void{{/returnType}}> _callback) throws ApiException { + public{{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}}private{{/vendorExtensions.x-group-parameters}} okhttp3.Call {{operationId}}Async({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}final ApiCallback<{{{returnType}}}{{^returnType}}Void{{/returnType}}> _callback) throws ApiException { okhttp3.Call localVarCall = {{operationId}}ValidateBeforeCall({{#allParams}}{{paramName}}, {{/allParams}}_callback); {{#returnType}}Type localVarReturnType = new TypeToken<{{{returnType}}}>(){}.getType(); @@ -405,13 +431,15 @@ public class {{classname}} { public class API{{operationId}}Request { {{#requiredParams}} + {{>nullable_var_annotations}}{{! prevent indent}} private final {{{dataType}}} {{paramName}}; {{/requiredParams}} {{#optionalParams}} + {{>nullable_var_annotations}}{{! prevent indent}} private {{{dataType}}} {{paramName}}; {{/optionalParams}} - private API{{operationId}}Request({{#requiredParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}) { + private API{{operationId}}Request({{#requiredParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}) { {{#requiredParams}} this.{{paramName}} = {{paramName}}; {{/requiredParams}} @@ -423,7 +451,7 @@ public class {{classname}} { * @param {{paramName}} {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}) * @return API{{operationId}}Request */ - public API{{operationId}}Request {{paramName}}({{{dataType}}} {{paramName}}) { + public API{{operationId}}Request {{paramName}}({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { this.{{paramName}} = {{paramName}}; return this; } @@ -436,7 +464,8 @@ public class {{classname}} { * @throws ApiException If fail to serialize the request body object {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -460,7 +489,8 @@ public class {{classname}} { * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -492,7 +522,8 @@ public class {{classname}} { * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -524,7 +555,8 @@ public class {{classname}} { * @throws ApiException If fail to process the API call, e.g. serializing the request body object {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -550,7 +582,8 @@ public class {{classname}} { * @return API{{operationId}}Request {{#responses.0}} * @http.response.details -
Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}
+
+ {{#responses}} @@ -568,7 +601,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public API{{operationId}}Request {{operationId}}({{#requiredParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}) { + public API{{operationId}}Request {{operationId}}({{#requiredParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}) { return new API{{operationId}}Request({{#requiredParams}}{{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}); } {{/vendorExtensions.x-group-parameters}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/apiException.mustache index dd224d582..96283bc05 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/apiException.mustache @@ -9,21 +9,23 @@ import java.util.Map.Entry; import java.util.TreeMap; {{/caseInsensitiveResponseHeaders}} -import javax.ws.rs.core.GenericType; /** *

ApiException class.

*/ @SuppressWarnings("serial") {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private Map> responseHeaders = null; private String responseBody = null; {{#errorObjectType}} private {{{errorObjectType}}} errorObject = null; {{/errorObjectType}} - + /** *

Constructor for ApiException.

*/ @@ -168,7 +170,7 @@ public class ApiException extends{{#useRuntimeException}} RuntimeException {{/us * @return The exception message */ public String getMessage() { - return String.format("Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s", + return String.format(java.util.Locale.ROOT, "Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s", super.getMessage(), this.getCode(), this.getResponseBody(), this.getResponseHeaders()); } {{#errorObjectType}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_doc.mustache index f97eab5c7..dea75bd2b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_doc.mustache @@ -10,7 +10,7 @@ All URIs are relative to *{{basePath}}* {{#operations}} {{#operation}} - + # **{{operationId}}**{{^vendorExtensions.x-group-parameters}} > {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}){{/vendorExtensions.x-group-parameters}}{{#vendorExtensions.x-group-parameters}} > {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#requiredParams}}{{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}){{#optionalParams}}.{{paramName}}({{paramName}}){{/optionalParams}}.execute();{{/vendorExtensions.x-group-parameters}} @@ -33,6 +33,10 @@ public class Example { public static void main(String[] args) { ApiClient defaultClient = Configuration.getDefaultApiClient(); defaultClient.setBasePath("{{{basePath}}}"); + {{#withAWSV4Signature}} + // Configure AWS Signature V4 authorization + defaultClient.setAWS4Configuration("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY", "REGION", "SERVICE") + {{/withAWSV4Signature}} {{#hasAuthMethods}} {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} // Configure HTTP basic authorization: {{{name}}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_test.mustache index f14f2cbc1..b56bdf4db 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/api_test.mustache @@ -8,7 +8,6 @@ import {{invokerPackage}}.ApiException; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -16,8 +15,12 @@ import java.util.Map; {{#supportStreaming}} import java.io.InputStream; {{/supportStreaming}} -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/AWS4Auth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/AWS4Auth.mustache new file mode 100644 index 000000000..d3f30b77c --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/AWS4Auth.mustache @@ -0,0 +1,105 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; +import {{invokerPackage}}.ApiException; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.List; +import java.util.stream.Collectors; + +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.auth.signer.params.Aws4SignerParams; +import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.regions.Region; + +import okio.Buffer; + +{{>generatedAnnotation}} + +public class AWS4Auth implements Authentication { + + private AwsCredentials credentials; + private String region; + private String service; + + public AWS4Auth() { + this.credentials = AnonymousCredentialsProvider.create().resolveCredentials(); + } + + public void setCredentials(String accessKey, String secretKey) { + this.credentials = AwsBasicCredentials.create(accessKey, secretKey); + } + + public void setCredentials(String accessKey, String secretKey, String sessionToken) { + this.credentials = AwsSessionCredentials.create(accessKey, secretKey, sessionToken); + } + + public void setRegion(String region) { + this.region = region; + } + + public void setService(String service) { + this.service = service; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, + Map cookieParams, String payload, String method, URI uri) + throws ApiException { + + SdkHttpFullRequest.Builder requestBuilder = + SdkHttpFullRequest.builder().uri(uri).method(SdkHttpMethod.fromValue(method)); + + ContentStreamProvider provider = new ContentStreamProvider() { + @Override + public InputStream newStream() { + InputStream is = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)); + return is; + } + }; + + requestBuilder = requestBuilder.contentStreamProvider(provider); + + SdkHttpFullRequest signableRequest = sign(requestBuilder); + + Map headers = signableRequest.headers().entrySet().stream() + .collect(Collectors.toMap(s -> s.getKey(), e -> e.getValue().get(0))); + + headerParams.putAll(headers); + } + + /** + * AWS Signature Version 4 signing. + * + * @param request {@link SdkHttpFullRequest.Builder} + * @return {@link SdkHttpFullRequest} + */ + private SdkHttpFullRequest sign(final SdkHttpFullRequest.Builder request) { + + SdkHttpFullRequest req = request.build(); + + if (this.service != null && this.region != null && this.credentials != null) { + Aws4SignerParams params = Aws4SignerParams.builder().signingName(this.service) + .signingRegion(Region.of(this.region)).awsCredentials(this.credentials).build(); + + Aws4Signer signer = Aws4Signer.create(); + + req = signer.sign(req, params); + } + + return req; + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/ApiKeyAuth.mustache index a0dda669a..ee02f7cec 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/ApiKeyAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/Authentication.mustache index c4ad338c7..da1e1902c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/Authentication.mustache @@ -9,6 +9,8 @@ import java.net.URI; import java.util.Map; import java.util.List; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and query params. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBasicAuth.mustache index 417a89e34..41f336707 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBasicAuth.mustache @@ -11,8 +11,6 @@ import java.net.URI; import java.util.Map; import java.util.List; -import java.io.UnsupportedEncodingException; - public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBearerAuth.mustache index c8a9fce29..c0a85528b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/HttpBearerAuth.mustache @@ -6,13 +6,16 @@ import {{invokerPackage}}.ApiException; import {{invokerPackage}}.Pair; import java.net.URI; -import java.util.Map; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; - private String bearerToken; + private Supplier tokenSupplier; public HttpBearerAuth(String scheme) { this.scheme = scheme; @@ -24,7 +27,7 @@ public class HttpBearerAuth implements Authentication { * @return The bearer token */ public String getBearerToken() { - return bearerToken; + return tokenSupplier.get(); } /** @@ -33,12 +36,22 @@ public class HttpBearerAuth implements Authentication { * @param bearerToken The bearer token to send in the Authorization header */ public void setBearerToken(String bearerToken) { - this.bearerToken = bearerToken; + this.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; } @Override public void applyToParams(List queryParams, Map headerParams, Map cookieParams, String payload, String method, URI uri) throws ApiException { + String bearerToken = Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); if (bearerToken == null) { return; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuth.mustache index 34d359857..f664362ef 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuth.mustache @@ -10,6 +10,7 @@ import java.util.Map; import java.util.List; {{>generatedAnnotation}} + public class OAuth implements Authentication { private String accessToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuthOkHttpClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuthOkHttpClient.mustache index f11b22c62..67d7f720d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuthOkHttpClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/OAuthOkHttpClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + {{#hasOAuthMethods}} package {{invokerPackage}}.auth; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/RetryingOAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/RetryingOAuth.mustache index 8fea0d292..fbe73e729 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/RetryingOAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/auth/RetryingOAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + {{#hasOAuthMethods}} package {{invokerPackage}}.auth; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.gradle.mustache index 2183fa8d7..6dfc02b9b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.gradle.mustache @@ -66,9 +66,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task) } } @@ -106,25 +106,41 @@ if(hasProperty('target') && target == 'android') { } ext { + {{#swagger1AnnotationLibrary}} + swagger_annotations_version = "1.6.9" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} jakarta_annotation_version = "1.3.5" + {{#useBeanValidation}} + bean_validation_version = "2.0.2" + {{/useBeanValidation}} } dependencies { - implementation 'io.swagger:swagger-annotations:1.6.8' + {{#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.squareup.okhttp3:okhttp:4.10.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' implementation 'com.google.code.gson:gson:2.9.1' - implementation 'io.gsonfire:gson-fire:1.8.5' - implementation 'javax.ws.rs:jsr311-api:1.1.1' - implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1' + implementation 'io.gsonfire:gson-fire:1.9.0' + implementation 'jakarta.ws.rs:jakarta.ws.rs-api:2.1.6' {{#openApiNullable}} - implementation 'org.openapitools:jackson-databind-nullable:0.2.4' + implementation 'org.openapitools:jackson-databind-nullable:0.2.9' {{/openApiNullable}} + {{#withAWSV4Signature}} + implementation 'software.amazon.awssdk:auth:2.20.157' + {{/withAWSV4Signature}} {{#hasOAuthMethods}} implementation group: 'org.apache.oltu.oauth2', name: 'org.apache.oltu.oauth2.client', version: '1.0.2' {{/hasOAuthMethods}} - implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.18.0' {{#joda}} implementation 'joda-time:joda-time:2.9.9' {{/joda}} @@ -132,9 +148,12 @@ dependencies { implementation 'io.swagger.parser.v3:swagger-parser-v3:2.0.30' {{/dynamicOperations}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1' + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$bean_validation_version" + {{/useBeanValidation}} + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' testImplementation 'org.mockito:mockito-core:3.12.4' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.3' } javadoc { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.sbt.mustache index 77d6183a9..0fd35a931 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/build.sbt.mustache @@ -3,22 +3,24 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( "io.swagger" % "swagger-annotations" % "1.6.5", - "com.squareup.okhttp3" % "okhttp" % "4.10.0", - "com.squareup.okhttp3" % "logging-interceptor" % "4.10.0", + "com.squareup.okhttp3" % "okhttp" % "4.12.0", + "com.squareup.okhttp3" % "logging-interceptor" % "4.12.0", "com.google.code.gson" % "gson" % "2.9.1", - "org.apache.commons" % "commons-lang3" % "3.12.0", - "javax.ws.rs" % "jsr311-api" % "1.1.1", - "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1", + "org.apache.commons" % "commons-lang3" % "3.18.0", + "jakarta.ws.rs" % "jakarta.ws.rs-api" % "2.1.6", {{#openApiNullable}} - "org.openapitools" % "jackson-databind-nullable" % "0.2.4", + "org.openapitools" % "jackson-databind-nullable" % "0.2.9", {{/openApiNullable}} + {{#withAWSV4Signature}} + "software.amazon.awssdk" % "auth" % "2.20.157", + {{/withAWSV4Signature}} {{#hasOAuthMethods}} "org.apache.oltu.oauth2" % "org.apache.oltu.oauth2.client" % "1.0.2", {{/hasOAuthMethods}} @@ -28,11 +30,11 @@ lazy val root = (project in file(".")). {{#dynamicOperations}} "io.swagger.parser.v3" % "swagger-parser-v3" "2.0.30" % "compile" {{/dynamicOperations}} - "io.gsonfire" % "gson-fire" % "1.8.5" % "compile", + "io.gsonfire" % "gson-fire" % "1.9.0" % "compile", "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", "com.google.code.findbugs" % "jsr305" % "3.0.2" % "compile", "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", - "org.junit.jupiter" % "junit-jupiter-api" % "5.9.1" % "test", + "org.junit.jupiter" % "junit-jupiter-api" % "5.10.3" % "test", "com.novocode" % "junit-interface" % "0.10" % "test", "org.mockito" % "mockito-core" % "3.12.4" % "test" ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model.mustache index b6b0381a5..3a1cca8d7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model.mustache @@ -7,48 +7,22 @@ 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.*; +import {{javaxPackage}}.xml.bind.annotation.*; {{/withXml}} -{{#vendorExtensions.x-has-readonly-properties}} -import com.fasterxml.jackson.annotation.JsonCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jackson}} -{{#withXml}} -import javax.xml.bind.annotation.*; -{{/withXml}} -{{#jsonb}} -import java.lang.reflect.Type; -import javax.json.bind.annotation.JsonbTypeDeserializer; -import javax.json.bind.annotation.JsonbTypeSerializer; -import javax.json.bind.serializer.DeserializationContext; -import javax.json.bind.serializer.JsonbDeserializer; -import javax.json.bind.serializer.JsonbSerializer; -import javax.json.bind.serializer.SerializationContext; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonParser; -import javax.json.bind.annotation.JsonbProperty; -{{#vendorExtensions.x-has-readonly-properties}} -import javax.json.bind.annotation.JsonbCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jsonb}} {{#parcelableModel}} import android.os.Parcelable; import android.os.Parcel; {{/parcelableModel}} {{#useBeanValidation}} -import javax.validation.constraints.*; -import javax.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} {{#performBeanValidation}} import org.hibernate.validator.constraints.*; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelEnum.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelEnum.mustache new file mode 100644 index 000000000..605f4f6a2 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelEnum.mustache @@ -0,0 +1,88 @@ +import java.io.IOException; +{{#isUri}} +import java.net.URI; +{{/isUri}} +import com.google.gson.TypeAdapter; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ +{{#isDeprecated}} +@Deprecated +{{/isDeprecated}} +@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) +{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#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; + } + + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + 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}} + } + + 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(){{#isUri}}.toASCIIString(){{/isUri}}); + } + + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}}{{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } + + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isUri}}URI.create({{/isUri}}jsonElement.{{#isNumber}}getAsString(){{/isNumber}}{{#isInteger}}getAsInt(){{/isInteger}}{{#isUri}}getAsString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}getAs{{{dataType}}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } +{{#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(java.util.Locale.ROOT, "%s=%s", prefix, this.toString()); + } +{{/supportUrlQuery}} +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelInnerEnum.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelInnerEnum.mustache new file mode 100644 index 000000000..d21f03c3c --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/modelInnerEnum.mustache @@ -0,0 +1,66 @@ + /** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ + @JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.Adapter.class) +{{#withXml}} + @XmlType(name="{{datatypeWithEnum}}") + @XmlEnum({{dataType}}.class) +{{/withXml}} + {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#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; + } + + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + 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}} + } + + 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(){{#isUri}}.toASCIIString(){{/isUri}}); + } + + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}} {{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } + + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isUri}}URI.create({{/isUri}}jsonElement.{{#isNumber}}getAsString(){{/isNumber}}{{#isInteger}}getAsInt(){{/isInteger}}{{#isUri}}getAsString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}getAs{{{dataType}}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model_test.mustache index ac4bf9a41..040d319bf 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/model_test.mustache @@ -7,13 +7,6 @@ package {{package}}; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -{{#fullJavaUtil}} -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -{{/fullJavaUtil}} - /** * Model tests for {{classname}} */ diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/oneof_model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/oneof_model.mustache index 9b94b365f..ea64a0e67 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/oneof_model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/oneof_model.mustache @@ -1,4 +1,4 @@ -import javax.ws.rs.core.GenericType; + import java.io.IOException; import java.lang.reflect.Type; @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.google.gson.Gson; @@ -27,12 +28,13 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonArray; import com.google.gson.JsonParseException; import {{invokerPackage}}.JSON; {{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}} -public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} { +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { private static final Logger log = Logger.getLogger({{classname}}.class.getName()); public static class CustomTypeAdapterFactory implements TypeAdapterFactory { @@ -43,9 +45,26 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return null; // this class only serializes '{{classname}}' and its subtypes } final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + {{#composedSchemas}} {{#oneOf}} - final TypeAdapter<{{.}}> adapter{{.}} = gson.getDelegateAdapter(this, TypeToken.get({{.}}.class)); + {{^isArray}} + {{^isMap}} + {{^vendorExtensions.x-duplicated-data-type}} + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}.class)); + {{/vendorExtensions.x-duplicated-data-type}} + {{/isMap}} + {{/isArray}} + {{#isArray}} + + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + {{/isArray}} + {{#isMap}} + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + {{/isMap}} {{/oneOf}} + {{/composedSchemas}} return (TypeAdapter) new TypeAdapter<{{classname}}>() { @Override @@ -55,25 +74,58 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return; } + {{#composedSchemas}} {{#oneOf}} - // check if the actual instance is of the type `{{.}}` - if (value.getActualInstance() instanceof {{.}}) { - JsonObject obj = adapter{{.}}.toJsonTree(({{.}})value.getActualInstance()).getAsJsonObject(); - elementAdapter.write(out, obj); + {{^vendorExtensions.x-duplicated-data-type}} + // check if the actual instance is of the type `{{{dataType}}}` + if (value.getActualInstance() instanceof {{#isArray}}List{{/isArray}}{{#isMap}}Map{{/isMap}}{{^isMap}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isMap}}) { + {{#isPrimitiveType}} + {{^isMap}} + JsonPrimitive primitive = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); + elementAdapter.write(out, primitive); return; + {{/isMap}} + {{#isMap}} + JsonObject object = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonObject(); + elementAdapter.write(out, object); + return; + {{/isMap}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + List list = (List) value.getActualInstance(); + if (!list.isEmpty() && list.get(0) instanceof {{{items.dataType}}}) { + JsonArray array = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); + elementAdapter.write(out, array); + return; + } + {{/isArray}} + {{/isPrimitiveType}} + {{^isMap}} + {{^isArray}} + {{^isPrimitiveType}} + JsonElement element = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()); + elementAdapter.write(out, element); + return; + {{/isPrimitiveType}} + {{/isArray}} + {{/isMap}} } - + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} throw new IOException("Failed to serialize as the type doesn't match oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); } @Override public {{classname}} read(JsonReader in) throws IOException { Object deserialized = null; - JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); + JsonElement jsonElement = elementAdapter.read(in); {{#useOneOfDiscriminatorLookup}} {{#discriminator}} + JsonObject jsonObject = jsonElement.getAsJsonObject(); + // use discriminator value for faster oneOf lookup {{classname}} new{{classname}} = new {{classname}}(); if (jsonObject.get("{{{propertyBaseName}}}") == null) { @@ -88,7 +140,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im return new{{classname}}; {{/mappedModels}} default: - log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", jsonObject.get("{{{propertyBaseName}}}").getAsString())); + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", jsonObject.get("{{{propertyBaseName}}}").getAsString())); } } @@ -98,56 +150,166 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im ArrayList errorMessages = new ArrayList<>(); TypeAdapter actualAdapter = elementAdapter; + {{#composedSchemas}} {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{^hasVars}} + // deserialize {{{dataType}}} + try { + // validate the JSON object to see if any exception is thrown + {{^isArray}} + {{^isMap}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#isUuid}} + UUID.fromString(jsonElement.getAsString()); + {{/isUuid}} + {{^isUuid}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); + {{/isUuid}} + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{/isMap}} + {{/isArray}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isArray}} + {{#isMap}} + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a object type in the JSON string but got `%s`", jsonElement.toString())); + } + + {{^isFreeFormObject}} + Map map = jsonElement.getAsJsonObject().asMap(); + // validate map items + for(JsonElement element : map.values()) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isFreeFormObject}} + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isMap}} + match++; + log.log(Level.FINER, "Input data matches schema '{{{dataType}}}'"); + } catch (Exception e) { + // deserialization failed, continue + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + log.log(Level.FINER, "Input data does not match schema '{{{dataType}}}'", e); + } + {{/hasVars}} + {{#hasVars}} // deserialize {{{.}}} try { // validate the JSON object to see if any exception is thrown - {{.}}.validateJsonObject(jsonObject); + {{.}}.validateJsonElement(jsonElement); actualAdapter = adapter{{.}}; match++; log.log(Level.FINER, "Input data matches schema '{{{.}}}'"); } catch (Exception e) { // deserialization failed, continue - errorMessages.add(String.format("Deserialization for {{{.}}} failed with `%s`.", e.getMessage())); + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{.}}} failed with `%s`.", e.getMessage())); log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); } - + {{/hasVars}} + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} + if (match == 1) { {{classname}} ret = new {{classname}}(); - ret.setActualInstance(actualAdapter.fromJsonTree(jsonObject)); + ret.setActualInstance(actualAdapter.fromJsonTree(jsonElement)); return ret; } - throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonObject.toString())); + throw new IOException(String.format(java.util.Locale.ROOT, "Failed deserialization for {{classname}}: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonElement.toString())); } }.nullSafe(); } } // store a list of schema names defined in oneOf - public static final Map schemas = new HashMap(); + public static final Map> schemas = new HashMap>(); public {{classname}}() { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); } - {{#oneOf}} - public {{classname}}({{{.}}} o) { + public {{classname}}(Object o) { super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); setActualInstance(o); } - {{/oneOf}} static { + {{#composedSchemas}} {{#oneOf}} - schemas.put("{{{.}}}", new GenericType<{{{.}}}>() { - }); + {{^vendorExtensions.x-duplicated-data-type}} + schemas.put("{{{dataType}}}", {{{baseType}}}.class); + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} } @Override - public Map getSchemas() { + public Map> getSchemas() { return {{classname}}.schemas; } @@ -157,7 +319,6 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * {{#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) { @@ -168,13 +329,26 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im } {{/isNullable}} + {{#composedSchemas}} {{#oneOf}} - if (instance instanceof {{{.}}}) { + {{^vendorExtensions.x-duplicated-data-type}} + if (instance instanceof {{#isArray}}List{{/isArray}}{{#isMap}}Map{{/isMap}}{{^isMap}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isMap}}) { + {{#isArray}} + List list = (List) instance; + if (!list.isEmpty() && list.get(0) instanceof {{{items.dataType}}}) { + super.setActualInstance(instance); + return; + } + {{/isArray}} + {{^isArray}} super.setActualInstance(instance); return; + {{/isArray}} } + {{/vendorExtensions.x-duplicated-data-type}} {{/oneOf}} + {{/composedSchemas}} throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); } @@ -184,67 +358,165 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * * @return The actual instance ({{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}) */ + @SuppressWarnings("unchecked") @Override public Object getActualInstance() { return super.getActualInstance(); } + {{#composedSchemas}} {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} /** - * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, * the ClassCastException will be thrown. * - * @return The actual instance of `{{{.}}}` - * @throws ClassCastException if the instance is not `{{{.}}}` + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{.}}} get{{{.}}}() throws ClassCastException { - return ({{{.}}})super.getActualInstance(); + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); } + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} {{/oneOf}} + {{/composedSchemas}} + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to {{classname}} + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + // validate oneOf schemas one by one + int validCount = 0; + ArrayList errorMessages = new ArrayList<>(); + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + // validate the json string with {{{dataType}}} + try { + {{^hasVars}} + {{^isMap}} + {{^isArray}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#isUuid}} + UUID.fromString(jsonElement.getAsString()); + {{/isUuid}} + {{^isUuid}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); + {{/isUuid}} + {{/isPrimitiveType}} + {{/isNumber}} + {{/isArray}} + {{/isMap}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isArray}} + {{#isMap}} + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be a object type in the JSON string but got `%s`", jsonElement.toString())); + } - /** - * Validates the JSON Object and throws an exception if issues found - * - * @param jsonObj JSON Object - * @throws IOException if the JSON Object is invalid with respect to {{classname}} - */ - public static void validateJsonObject(JsonObject jsonObj) throws IOException { - // validate oneOf schemas one by one - int validCount = 0; - ArrayList errorMessages = new ArrayList<>(); - {{#oneOf}} - // validate the json string with {{{.}}} - try { - {{{.}}}.validateJsonObject(jsonObj); - validCount++; - } catch (Exception e) { - errorMessages.add(String.format("Deserialization for {{{.}}} failed with `%s`.", e.getMessage())); - // continue to the next one + {{^isFreeFormObject}} + Map map = jsonElement.getAsJsonObject().asMap(); + // validate map items + for(JsonElement element : map.values()) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isFreeFormObject}} + {{/isMap}} + {{/hasVars}} + {{#hasVars}} + {{{.}}}.validateJsonElement(jsonElement); + validCount++; + {{/hasVars}} + validCount++; + } catch (Exception e) { + errorMessages.add(String.format(java.util.Locale.ROOT, "Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + // continue to the next one + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + if (validCount != 1) { + throw new IOException(String.format(java.util.Locale.ROOT, "The JSON string is invalid for {{classname}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. %d class(es) match the result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", validCount, errorMessages, jsonElement.toString())); + } } - {{/oneOf}} - if (validCount != 1) { - throw new IOException(String.format("The JSON string is invalid for {{classname}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. %d class(es) match the result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", validCount, errorMessages, jsonObj.toString())); + + /** + * Create an instance of {{classname}} given an JSON string + * + * @param jsonString JSON string + * @return An instance of {{classname}} + * @throws IOException if the JSON string is invalid with respect to {{classname}} + */ + public static {{{classname}}} fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); + } + + /** + * Convert an instance of {{classname}} to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); } - } - - /** - * Create an instance of {{classname}} given an JSON string - * - * @param jsonString JSON string - * @return An instance of {{classname}} - * @throws IOException if the JSON string is invalid with respect to {{classname}} - */ - public static {{{classname}}} fromJson(String jsonString) throws IOException { - return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); - } - - /** - * Convert an instance of {{classname}} to an JSON string - * - * @return JSON string - */ - public String toJson() { - return JSON.getGson().toJson(this); - } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pojo.mustache index e41ffd9d7..cb19eb603 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pojo.mustache @@ -8,13 +8,15 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; -import java.lang.reflect.Type; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import {{invokerPackage}}.JSON; @@ -29,16 +31,11 @@ import {{invokerPackage}}.JSON; @ApiModel(description = "{{{.}}}") {{/description}} {{/swagger1AnnotationLibrary}} -{{#jackson}} -@JsonPropertyOrder({ -{{#vars}} - {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} -{{/vars}} -}) -{{#isClassnameSanitized}} -@JsonTypeName("{{name}}") -{{/isClassnameSanitized}} -{{/jackson}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} {{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} @@ -52,62 +49,31 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#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}} - {{/isXmlAttribute}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{/withXml}} - {{#gson}} + {{#deprecated}} + @Deprecated + {{/deprecated}} @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}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}}; - {{/isContainer}} - {{^isContainer}} + {{>nullable_var_annotations}}{{! prevent indent}} {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} - {{/vendorExtensions.x-is-jackson-optional-nullable}} {{/vars}} public {{classname}}() { @@ -116,21 +82,23 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens super(); {{/parcelableModel}} {{/parent}} - {{#gson}} {{#discriminator}} + {{#discriminator.isEnum}} +{{#readWriteVars}}{{#isDiscriminator}}{{#defaultValue}} + this.{{name}} = {{defaultValue}}; +{{/defaultValue}}{{/isDiscriminator}}{{/readWriteVars}} + {{/discriminator.isEnum}} {{^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("{{baseName}}"){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} {{/readOnlyVars}} ) { this(); @@ -143,64 +111,36 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#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}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; 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}}}); - } - 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}} - {{^required}} + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; } - {{/required}} 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}}}); - } - 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}} + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}; + 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}} @@ -217,80 +157,40 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{#deprecated}} * @deprecated {{/deprecated}} - **/ + */ {{#deprecated}} @Deprecated {{/deprecated}} -{{#required}} -{{#isNullable}} - @javax.annotation.Nullable -{{/isNullable}} -{{^isNullable}} - @javax.annotation.Nonnull -{{/isNullable}} -{{/required}} -{{^required}} - @javax.annotation.Nullable -{{/required}} -{{#jsonb}} - @JsonbProperty("{{baseName}}") -{{/jsonb}} + {{>nullable_var_annotations}}{{! prevent indent}} {{#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}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#deprecated}} @Deprecated +{{/deprecated}} public void {{setter}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { this.{{name}} = {{name}}; - {{/vendorExtensions.x-is-jackson-optional-nullable}} } {{/isReadOnly}} {{/vars}} {{>libraries/okhttp-gson/additional_properties}} + @Override public boolean equals(Object o) { {{#useReflectionEqualsHashCode}} @@ -304,7 +204,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}} && + return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{^-last}} && {{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}&& Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/isAdditionalPropertiesTrue}}{{#parent}} && super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} @@ -322,7 +222,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens 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}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}}); + return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}}); {{/useReflectionEqualsHashCode}} }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} @@ -341,7 +241,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens sb.append(" ").append(toIndentedString(super.toString())).append("\n"); {{/parent}} {{#vars}} - sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); {{/vars}} {{#isAdditionalPropertiesTrue}} sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); @@ -354,7 +254,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens * 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) { + private String toIndentedString(Object o) { if (o == null) { return "null"; } @@ -426,38 +326,42 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens static { // a set of all properties/fields (JSON key names) - openapiFields = new HashSet(); - {{#allVars}} - openapiFields.add("{{baseName}}"); - {{/allVars}} + {{#hasVars}} + openapiFields = new HashSet(Arrays.asList({{#allVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/allVars}})); + {{/hasVars}} + {{^hasVars}} + openapiFields = new HashSet(0); + {{/hasVars}} // a set of required properties/fields (JSON key names) - openapiRequiredFields = new HashSet(); - {{#requiredVars}} - openapiRequiredFields.add("{{baseName}}"); - {{/requiredVars}} + {{#hasRequired}} + openapiRequiredFields = new HashSet(Arrays.asList({{#requiredVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/requiredVars}})); + {{/hasRequired}} + {{^hasRequired}} + openapiRequiredFields = new HashSet(0); + {{/hasRequired}} } - /** - * Validates the JSON Object and throws an exception if issues found - * - * @param jsonObj JSON Object - * @throws IOException if the JSON Object is invalid with respect to {{classname}} - */ - public static void validateJsonObject(JsonObject jsonObj) throws IOException { - if (jsonObj == null) { - if (!{{classname}}.openapiRequiredFields.isEmpty()) { // has required fields but JSON object is null - throw new IllegalArgumentException(String.format("The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString())); + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to {{classname}} + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!{{classname}}.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString())); } } {{^hasChildren}} {{^isAdditionalPropertiesTrue}} - Set> entries = jsonObj.entrySet(); + Set> entries = jsonElement.getAsJsonObject().entrySet(); // check to see if the JSON string contains additional fields - for (Entry entry : entries) { + for (Map.Entry entry : entries) { if (!{{classname}}.openapiFields.contains(entry.getKey())) { - throw new IllegalArgumentException(String.format("The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", entry.getKey(), jsonObj.toString())); + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", entry.getKey(), jsonElement.toString())); } } {{/isAdditionalPropertiesTrue}} @@ -466,28 +370,31 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens // check to make sure all required properties/fields are present in the JSON string for (String requiredField : {{classname}}.openapiRequiredFields) { - if (jsonObj.get(requiredField) == null) { - throw new IllegalArgumentException(String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonObj.toString())); + if (jsonElement.getAsJsonObject().get(requiredField) == null) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())); } } {{/-first}} {{/requiredVars}} {{/hasChildren}} {{^discriminator}} + {{#hasVars}} + JsonObject jsonObj = jsonElement.getAsJsonObject(); + {{/hasVars}} {{#vars}} {{#isArray}} {{#items.isModel}} {{#required}} - // ensure the json data is an array - if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { - throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + if (jsonObj.get("{{{baseName}}}") != null{{#isNullable}} && !jsonObj.get("{{{baseName}}}").isJsonNull(){{/isNullable}}) { + if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}"); + // validate the required field `{{{baseName}}}` (array) + for (int i = 0; i < jsonArray{{name}}.size(); i++) { + {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i)); + } } - - JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}"); - // validate the required field `{{{baseName}}}` (array) - for (int i = 0; i < jsonArray{{name}}.size(); i++) { - {{{items.dataType}}}.validateJsonObject(jsonArray{{name}}.get(i).getAsJsonObject()); - }; {{/required}} {{^required}} if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { @@ -495,12 +402,12 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens if (jsonArray{{name}} != null) { // ensure the json data is an array if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { - throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); } // validate the optional field `{{{baseName}}}` (array) for (int i = 0; i < jsonArray{{name}}.size(); i++) { - {{{items.dataType}}}.validateJsonObject(jsonArray{{name}}.get(i).getAsJsonObject()); + {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i)); }; } } @@ -509,53 +416,95 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens {{^items.isModel}} {{^required}} // ensure the optional json data is an array if present - if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonArray()) { - throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull() && !jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); } {{/required}} {{#required}} // ensure the required json array is present if (jsonObj.get("{{{baseName}}}") == null) { throw new IllegalArgumentException("Expected the field `linkedContent` to be an array in the JSON string but got `null`"); - } else if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { - throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } else if (!jsonObj.get("{{{baseName}}}").isJsonArray(){{#isNullable}} && !jsonObj.get("{{baseName}}").isJsonNull(){{/isNullable}}) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); } {{/required}} {{/items.isModel}} {{/isArray}} {{^isContainer}} {{#isString}} - if ({{^required}}(jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) && {{/required}}!jsonObj.get("{{{baseName}}}").isJsonPrimitive()) { - throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + if ({{#notRequiredOrIsNullable}}(jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) && {{/notRequiredOrIsNullable}}!jsonObj.get("{{{baseName}}}").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); } {{/isString}} {{#isModel}} {{#required}} + {{#isNullable}} + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{/isNullable}} // validate the required field `{{{baseName}}}` - {{{dataType}}}.validateJsonObject(jsonObj.getAsJsonObject("{{{baseName}}}")); + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{#isNullable}} + } + {{/isNullable}} {{/required}} {{^required}} // validate the optional field `{{{baseName}}}` if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { - {{{dataType}}}.validateJsonObject(jsonObj.getAsJsonObject("{{{baseName}}}")); + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); } {{/required}} {{/isModel}} + {{#isEnum}} + {{#required}} + {{#isNullable}} + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{/isNullable}} + // validate the required field `{{{baseName}}}` + {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{#isNullable}} + } + {{/isNullable}} + {{/required}} + {{^required}} + // validate the optional field `{{{baseName}}}` + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + } + {{/required}} + {{/isEnum}} + {{#isEnumRef}} + {{#required}} + {{#isNullable}} + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{/isNullable}} + // validate the required field `{{{baseName}}}` + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{#isNullable}} + } + {{/isNullable}} + {{/required}} + {{^required}} + // validate the optional field `{{{baseName}}}` + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + } + {{/required}} + {{/isEnumRef}} {{/isContainer}} {{/vars}} {{/discriminator}} {{#hasChildren}} {{#discriminator}} - String discriminatorValue = jsonObj.get("{{{propertyBaseName}}}").getAsString(); + String discriminatorValue = jsonElement.getAsJsonObject().get("{{{propertyBaseName}}}").getAsString(); switch (discriminatorValue) { {{#mappedModels}} case "{{mappingName}}": - {{modelName}}.validateJsonObject(jsonObj); + {{modelName}}.validateJsonElement(jsonElement); break; {{/mappedModels}} - default: - throw new IllegalArgumentException(String.format("The value of the `{{{propertyBaseName}}}` field `%s` does not match any key defined in the discriminator's mapping.", discriminatorValue)); + default: + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The value of the `{{{propertyBaseName}}}` field `%s` does not match any key defined in the discriminator's mapping.", discriminatorValue)); } {{/discriminator}} {{/hasChildren}} @@ -591,7 +540,12 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens else if (entry.getValue() instanceof Character) obj.addProperty(entry.getKey(), (Character) entry.getValue()); else { - obj.add(entry.getKey(), gson.toJsonTree(entry.getValue()).getAsJsonObject()); + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } } } } @@ -601,9 +555,10 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens @Override public {{classname}} read(JsonReader in) throws IOException { - JsonObject jsonObj = elementAdapter.read(in).getAsJsonObject(); - validateJsonObject(jsonObj); + JsonElement jsonElement = elementAdapter.read(in); + validateJsonElement(jsonElement); {{#isAdditionalPropertiesTrue}} + JsonObject jsonObj = jsonElement.getAsJsonObject(); // store additional fields in the deserialized instance {{classname}} instance = thisAdapter.fromJsonTree(jsonObj); for (Map.Entry entry : jsonObj.entrySet()) { @@ -616,7 +571,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens else if (entry.getValue().getAsJsonPrimitive().isBoolean()) instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); else - throw new IllegalArgumentException(String.format("The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); } else if (entry.getValue().isJsonArray()) { instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); } else { // JSON object @@ -627,7 +582,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens return instance; {{/isAdditionalPropertiesTrue}} {{^isAdditionalPropertiesTrue}} - return thisAdapter.fromJsonTree(jsonObj); + return thisAdapter.fromJsonTree(jsonElement); {{/isAdditionalPropertiesTrue}} } @@ -636,22 +591,22 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } {{/hasChildren}} - /** - * Create an instance of {{classname}} given an JSON string - * - * @param jsonString JSON string - * @return An instance of {{classname}} - * @throws IOException if the JSON string is invalid with respect to {{classname}} - */ + /** + * Create an instance of {{classname}} given an JSON string + * + * @param jsonString JSON string + * @return An instance of {{classname}} + * @throws IOException if the JSON string is invalid with respect to {{classname}} + */ public static {{{classname}}} fromJson(String jsonString) throws IOException { return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); } - /** - * Convert an instance of {{classname}} to an JSON string - * - * @return JSON string - */ + /** + * Convert an instance of {{classname}} to an JSON string + * + * @return JSON string + */ public String toJson() { return JSON.getGson().toJson(this); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pom.mustache index 2f0fb09e7..407658ff5 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/okhttp-gson/pom.mustache @@ -57,7 +57,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.1.0 + 3.4.1 enforce-maven @@ -79,12 +79,12 @@ maven-surefire-plugin 2.22.2 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods 10 @@ -100,7 +100,7 @@ maven-dependency-plugin - 3.3.0 + 3.6.1 package @@ -131,7 +131,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.3.0 + 3.5.0 add_sources @@ -162,7 +162,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.4.1 + 3.6.3 attach-javadocs @@ -185,7 +185,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.0 attach-sources @@ -248,7 +248,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.0.1 + 3.2.1 sign-artifacts @@ -272,6 +272,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} com.google.code.findbugs @@ -336,9 +343,9 @@ {{#performBeanValidation}} - org.hibernate + org.hibernate.validator hibernate-validator - 5.4.3.Final + 8.0.3.Final jakarta.el @@ -368,15 +375,17 @@ ${jackson-databind-nullable-version} {{/openApiNullable}} + {{#withAWSV4Signature}} - javax.ws.rs - jsr311-api - ${jsr311-api-version} + software.amazon.awssdk + auth + 2.20.157 + {{/withAWSV4Signature}} - javax.ws.rs - javax.ws.rs-api - ${javax.ws.rs-api-version} + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs-api-version} @@ -391,41 +400,43 @@ ${junit-platform-runner.version} test - - org.mockito - mockito-core - ${mockito-core-version} - test - 1.8 ${java.version} ${java.version} - 1.8.5 + 1.9.0 + {{#swagger1AnnotationLibrary}} 1.6.6 - 4.10.0 - 2.9.1 - 3.12.0 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 4.12.0 + 2.10.1 + 3.18.0 {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} {{#joda}} 2.12.0 {{/joda}} + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 -{{#performBeanValidation}} - 3.0.3 -{{/performBeanValidation}} -{{#useBeanValidation}} 2.0.2 -{{/useBeanValidation}} - 5.9.1 - 1.9.1 - 3.12.4 - 2.1.1 + {{/useJakartaEe}} + {{#performBeanValidation}} + 3.0.3 + {{/performBeanValidation}} + 5.10.3 + 1.10.0 + 2.1.6 1.1.1 UTF-8 - 2.27.2 + 2.43.0 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/ApiClient.mustache index 2a0f41737..c52ee9bae 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/ApiClient.mustache @@ -6,7 +6,6 @@ import {{apiPackage}}.*; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import io.restassured.builder.RequestSpecBuilder; import java.util.function.Consumer; import java.util.function.Supplier; @@ -15,16 +14,14 @@ import static io.restassured.config.ObjectMapperConfig.objectMapperConfig; import static io.restassured.config.RestAssuredConfig.config; import static {{invokerPackage}}.{{#gson}}GsonObjectMapper.gson{{/gson}}{{#jackson}}JacksonObjectMapper.jackson{{/jackson}}; -{{/fullJavaUtil}} - public class ApiClient { {{#basePath}} public static final String BASE_URI = "{{.}}"; {{/basePath}} - private final Config config; + protected final Config config; - private ApiClient(Config config) { + protected ApiClient(Config config) { this.config = config; } @@ -41,7 +38,7 @@ public class ApiClient { {{/apiInfo}} public static class Config { - private Supplier reqSpecSupplier = () -> new RequestSpecBuilder() + protected Supplier reqSpecSupplier = () -> new RequestSpecBuilder() {{#basePath}}.setBaseUri(BASE_URI){{/basePath}} .setConfig(config().objectMapperConfig(objectMapperConfig().defaultObjectMapper({{#gson}}gson(){{/gson}}{{#jackson}}jackson(){{/jackson}}))); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/JacksonObjectMapper.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/JacksonObjectMapper.mustache index 8919eda30..3d875d66b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/JacksonObjectMapper.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/JacksonObjectMapper.mustache @@ -27,7 +27,7 @@ public class JacksonObjectMapper extends Jackson2Mapper { ObjectMapper mapper = new ObjectMapper(); mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api.mustache index 0c8736061..e6da4fcee 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api.mustache @@ -8,7 +8,6 @@ import com.google.gson.reflect.TypeToken; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -25,12 +24,23 @@ import io.restassured.response.Response; {{#swagger1AnnotationLibrary}} import io.swagger.annotations.*; {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.enums.*; +import io.swagger.v3.oas.annotations.media.*; +import io.swagger.v3.oas.annotations.responses.*; +import io.swagger.v3.oas.annotations.security.*; +{{/swagger2AnnotationLibrary}} + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} import java.lang.reflect.Type; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -{{/fullJavaUtil}} {{#gson}} import {{invokerPackage}}.JSON; {{/gson}} @@ -39,6 +49,9 @@ import static io.restassured.http.Method.*; {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}") {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +@Tag(name = "{{{baseName}}}") +{{/swagger2AnnotationLibrary}} public class {{classname}} { private Supplier reqSpecSupplier; @@ -80,6 +93,14 @@ public class {{classname}} { @ApiResponses(value = { {{#responses}} @ApiResponse(code = {{{code}}}, message = "{{{message}}}") {{^-last}},{{/-last}}{{/responses}} }) {{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} + @Operation(summary = "{{{summary}}}", + description = "{{{notes}}}", + operationId = "{{{operationId}}}", + tags = { {{#tags}}{{#name}}"{{{.}}}"{{/name}}{{^-last}}, {{/-last}}{{/tags}} }) + @ApiResponses(value = { {{#responses}} + @ApiResponse(responseCode = "{{{code}}}", description = "{{{message}}}") {{^-last}},{{/-last}}{{/responses}} }) +{{/swagger2AnnotationLibrary}} {{#isDeprecated}} @Deprecated {{/isDeprecated}} @@ -125,7 +146,7 @@ public class {{classname}} { public static class {{operationIdCamelCase}}Oper implements Oper { public static final Method REQ_METHOD = {{httpMethod}}; - public static final String REQ_URI = "{{path}}"; + public static final String REQ_URI = "{{{path}}}"; private RequestSpecBuilder reqSpec; private ResponseSpecBuilder respSpec; @@ -134,17 +155,15 @@ public class {{classname}} { this.reqSpec = reqSpec; {{#vendorExtensions}} {{#x-content-type}} - reqSpec.setContentType("{{x-content-type}}"); + reqSpec.setContentType("{{{x-content-type}}}"); {{/x-content-type}} - {{#x-accepts}} - reqSpec.setAccept("{{x-accepts}}"); - {{/x-accepts}} + reqSpec.setAccept("{{#x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/x-accepts}}"); {{/vendorExtensions}} this.respSpec = new ResponseSpecBuilder(); } /** - * {{httpMethod}} {{path}} + * {{httpMethod}} {{{path}}} * @param handler handler * @param type * @return type @@ -156,7 +175,7 @@ public class {{classname}} { {{#returnType}} /** - * {{httpMethod}} {{path}} + * {{httpMethod}} {{{path}}} * @param handler handler * @return {{returnType}} */ @@ -172,7 +191,7 @@ public class {{classname}} { * @param {{paramName}} ({{dataType}}) {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} * @return operation */ - public {{operationIdCamelCase}}Oper body({{{dataType}}} {{paramName}}) { + public {{operationIdCamelCase}}Oper body({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { reqSpec.setBody({{paramName}}); return this; } @@ -240,7 +259,7 @@ public class {{classname}} { * @param {{paramName}} ({{dataType}}) {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} * @return operation */ - public {{operationIdCamelCase}}Oper {{paramName}}MultiPart({{{dataType}}} {{paramName}}) { + public {{operationIdCamelCase}}Oper {{paramName}}MultiPart({{>nullable_var_annotations}} {{{dataType}}} {{paramName}}) { reqSpec.addMultiPart({{paramName}}); return this; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_doc.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_doc.mustache index b3ca8dfab..42d223aea 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_doc.mustache @@ -10,7 +10,7 @@ All URIs are relative to *{{basePath}}* {{#operations}} {{#operation}} - + # **{{operationId}}** > {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_test.mustache index af38dc833..adcbd8085 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/api_test.mustache @@ -8,18 +8,22 @@ import {{invokerPackage}}.ApiClient; import {{apiPackage}}.{{classname}}; import io.restassured.builder.RequestSpecBuilder; import io.restassured.filter.log.ErrorLoggingFilter; -import org.junit.Before; -import org.junit.Test; -import org.junit.Ignore; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} import static io.restassured.config.ObjectMapperConfig.objectMapperConfig; import static io.restassured.config.RestAssuredConfig.config; import static {{invokerPackage}}.{{#gson}}GsonObjectMapper.gson{{/gson}}{{#jackson}}JacksonObjectMapper.jackson{{/jackson}}; @@ -27,12 +31,12 @@ import static {{invokerPackage}}.{{#gson}}GsonObjectMapper.gson{{/gson}}{{#jacks /** * API tests for {{classname}} */ -@Ignore +@Disabled public class {{classname}}Test { private {{classname}} api; - @Before + @BeforeEach public void createApi() { api = ApiClient.api(ApiClient.Config.apiConfig().reqSpecSupplier( () -> new RequestSpecBuilder() diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.gradle.mustache index 67a9e7191..5dedd464a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -97,29 +97,39 @@ if(hasProperty('target') && target == 'android') { } ext { - swagger_annotations_version = "1.6.6" - rest_assured_version = "4.5.1" - junit_version = "4.13.2" + {{#swagger1AnnotationLibrary}} + swagger_annotations_version = "1.6.16" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.36" + {{/swagger2AnnotationLibrary}} + rest_assured_version = "5.5.6" + junit_version = "5.10.3" {{#jackson}} - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} - jakarta_annotation_version = "1.3.5" {{/jackson}} {{#gson}} - gson_version = "2.8.9" - gson_fire_version = "1.8.5" + gson_version = "2.10.1" + gson_fire_version = "1.9.0" {{/gson}} {{#joda}} jodatime_version = "2.10.5" {{/joda}} - okio_version = "1.17.5" + okio_version = "3.6.0" + jakarta_annotation_version = "1.3.5" } 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 "io.rest-assured:rest-assured:$rest_assured_version" {{#jackson}} @@ -147,11 +157,11 @@ dependencies { {{/joda}} implementation "com.squareup.okio:okio:$okio_version" {{#useBeanValidation}} - implementation "jakarta.validation:jakarta.validation-api:2.0.2" + implementation "jakarta.validation:jakarta.validation-api:3.0.2" {{/useBeanValidation}} {{#performBeanValidation}} implementation "org.hibernate:hibernate-validator:6.0.19.Final" {{/performBeanValidation}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.sbt.mustache index 820edf8d6..e60fec0f9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/build.sbt.mustache @@ -3,22 +3,22 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( - "io.swagger" % "swagger-annotations" % "1.6.6", - "io.rest-assured" % "rest-assured" % "4.5.1", - "io.rest-assured" % "scala-support" % "4.5.1", + "io.swagger" % "swagger-annotations" % "1.6.16", + "io.rest-assured" % "rest-assured" % "5.5.6", + "io.rest-assured" % "scala-support" % "5.5.6", "com.google.code.findbugs" % "jsr305" % "3.0.2", {{#jackson}} - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.4", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.4", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.2", + "com.fasterxml.jackson.core" % "jackson-core" % "2.19.2", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.19.2", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.19.2", {{#openApiNullable}} - "org.openapitools" % "jackson-databind-nullable" % "0.2.4", + "org.openapitools" % "jackson-databind-nullable" % "0.2.9", {{/openApiNullable}} {{#withXml}} "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % "2.13.4.1", @@ -30,20 +30,20 @@ lazy val root = (project in file(".")). {{/jackson}} {{#gson}} "com.google.code.gson" % "gson" % "2.8.9", - "io.gsonfire" % "gson-fire" % "1.8.5" % "compile", + "io.gsonfire" % "gson-fire" % "1.9.0" % "compile", {{/gson}} {{#joda}} "joda-time" % "joda-time" % "2.10.5" % "compile", {{/joda}} "com.squareup.okio" % "okio" % "1.17.5" % "compile", {{#useBeanValidation}} - "jakarta.validation" % "jakarta.validation-api" % "2.0.2" % "compile", + "jakarta.validation" % "jakarta.validation-api" % "3.0.2" % "compile", {{/useBeanValidation}} {{#performBeanValidation}} "org.hibernate" % "hibernate-validator" % "6.0.19.Final" % "compile", {{/performBeanValidation}} "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", - "junit" % "junit" % "4.13.2" % "test", + "org.junit.jupiter" % "junit-jupiter-api" % "5.10.3" % "test", "com.novocode" % "junit-interface" % "0.10" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/pom.mustache index 2fc5a394d..7963ce2da 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/rest-assured/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 2.22.2 - + loggerPath conf/log4j.properties - + false 1C @@ -226,6 +226,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} com.google.code.findbugs @@ -317,38 +324,47 @@ {{#performBeanValidation}} - org.hibernate + org.hibernate.validator hibernate-validator - 6.0.19.Final + 8.0.3.Final {{/performBeanValidation}} - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test UTF-8 - 1.6.6 - 4.5.1 - 2.8.9 - 1.8.5 + {{#swagger1AnnotationLibrary}} + 1.6.16 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.36 + {{/swagger2AnnotationLibrary}} + 5.5.6 + 2.10.1 + 1.9.0 {{#joda}} 2.10.5 {{/joda}} {{#jackson}} - 2.13.4 - 2.13.4.2 - 0.2.4 + 2.19.2 + 2.19.2 + 0.2.9 {{/jackson}} + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 -{{#useBeanValidation}} 2.0.2 -{{/useBeanValidation}} - 1.17.5 - 4.13.2 + {{/useJakartaEe}} + 3.6.0 + 5.10.3 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/ApiClient.mustache new file mode 100644 index 000000000..04f7f9bb2 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/ApiClient.mustache @@ -0,0 +1,902 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +{{/withXml}} +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.util.function.Consumer; +{{#openApiNullable}} +import org.openapitools.jackson.nullable.JsonNullableModule; +{{/openApiNullable}} +{{#generateClientAsBean}} +import org.springframework.beans.factory.annotation.Autowired; +{{/generateClientAsBean}} +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.InvalidMediaTypeException; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +{{#withXml}} + import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; +{{/withXml}} +{{#generateClientAsBean}} +import org.springframework.stereotype.Component; +{{/generateClientAsBean}} +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.ResponseSpec; +import java.util.Optional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; +import java.util.function.Supplier; + +import {{javaxPackage}}.annotation.Nullable; + +{{#jsr310}} +import java.time.OffsetDateTime; +{{/jsr310}} + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.HttpBearerAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +{{#hasOAuthMethods}} +import {{invokerPackage}}.auth.OAuth; +{{/hasOAuthMethods}} + +{{>generatedAnnotation}} + +{{#generateClientAsBean}} +@Component("{{invokerPackage}}.ApiClient") +{{/generateClientAsBean}} +public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { + public enum CollectionFormat { + CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null); + + protected final String separator; + CollectionFormat(String separator) { + this.separator = separator; + } + + protected String collectionToString(Collection collection) { + return StringUtils.collectionToDelimitedString(collection, separator); + } + } + + protected final HttpHeaders defaultHeaders = new HttpHeaders(); + protected final MultiValueMap defaultCookies = new LinkedMultiValueMap<>(); + + protected 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; + + protected final RestClient restClient; + protected final DateFormat dateFormat; + protected final ObjectMapper objectMapper; + + protected Map authentications; + + + public ApiClient() { + this(null); + } + + {{#generateClientAsBean}} + @Autowired + {{/generateClientAsBean}} + public ApiClient(RestClient restClient) { + this(restClient, createDefaultDateFormat()); + } + + public ApiClient(ObjectMapper mapper, DateFormat format) { + this(null, mapper, format); + } + + public ApiClient(RestClient restClient, ObjectMapper mapper, DateFormat format) { + this.objectMapper = mapper.copy(); + this.restClient = Optional.ofNullable(restClient).orElseGet(() -> buildRestClient(this.objectMapper)); + this.dateFormat = format; + this.init(); + } + + protected ApiClient(RestClient restClient, DateFormat format) { + this(restClient, createDefaultObjectMapper(format), format); + } + + public static DateFormat createDefaultDateFormat() { + DateFormat dateFormat = new RFC3339DateFormat(); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat; + } + + public static ObjectMapper createDefaultObjectMapper(@Nullable DateFormat dateFormat) { + if (null == dateFormat) { + dateFormat = createDefaultDateFormat(); + } + ObjectMapper mapper = new ObjectMapper(); + mapper.setDateFormat(dateFormat); + mapper.registerModule(new JavaTimeModule()); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); + {{#openApiNullable}} + JsonNullableModule jnm = new JsonNullableModule(); + mapper.registerModule(jnm); + {{/openApiNullable}} + return mapper; + } + + protected void init() { + // 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}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Build the RestClientBuilder used to make RestClient. + * @param mapper ObjectMapper used for serialize/deserialize + * @return RestClient + */ + public static RestClient.Builder buildRestClientBuilder(ObjectMapper mapper) { + {{#withXml}} + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); + {{#openApiNullable}} + xmlMapper.registerModule(new JsonNullableModule()); + {{/openApiNullable}} + + {{/withXml}} + Consumer>> messageConverters = converters -> { + converters.add(0, new MappingJackson2HttpMessageConverter(mapper)); + {{#withXml}} + converters.add(0, new MappingJackson2XmlHttpMessageConverter(xmlMapper)); + {{/withXml}} + }; + + return RestClient.builder().messageConverters(messageConverters); + } + + /** + * Build the RestClientBuilder used to make RestClient. + * @return RestClient + */ + public static RestClient.Builder buildRestClientBuilder() { + return buildRestClientBuilder(createDefaultObjectMapper(null)); + } + + /** + * Build the RestClient used to make HTTP requests. + * @param mapper ObjectMapper used for serialize/deserialize + * @return RestClient + */ + public static RestClient buildRestClient(ObjectMapper mapper) { + return buildRestClientBuilder(mapper).build(); + } + + /** + * Build the RestClient used to make HTTP requests. + * @return RestClient + */ + public static RestClient buildRestClient() { + return buildRestClientBuilder(createDefaultObjectMapper(null)).build(); + } + + /** + * Get the current base path + * @return String the base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set the base path, which should include the host + * @param basePath the base path + * @return ApiClient this client + */ + 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; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map the currently configured authentication types + */ + 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); + } + + /** + * Helper method to set access token for the first Bearer authentication. + * @param bearerToken Bearer token + */ + 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!"); + } + + /** + * Helper method to set the supplier of access tokens for Bearer authentication. + * + * @param tokenSupplier the token supplier function + */ + public void setBearerToken(Supplier tokenSupplier) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBearerAuth) { + ((HttpBearerAuth) auth).setBearerToken(tokenSupplier); + return; + } + } + throw new RuntimeException("No Bearer authentication configured!"); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username the 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 the 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!"); + } + + /** + * 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 the 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!"); + } + + {{#hasOAuthMethods}} + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken the access token + */ + public void setAccessToken(String accessToken) { + setAccessToken(() -> accessToken); + } + + /** + * Helper method to set the supplier of access tokens for OAuth2 authentication. + * + * @param tokenSupplier The supplier of access tokens + * @return ApiClient this client + */ + public ApiClient setAccessToken(Supplier tokenSupplier) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(tokenSupplier); + return this; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + {{/hasOAuthMethods}} + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent the user agent string + * @return ApiClient this client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param name The header's name + * @param value The header's value + * @return ApiClient this client + */ + public ApiClient addDefaultHeader(String name, String value) { + defaultHeaders.set(name, value); + return this; + } + + /** + * Add a default cookie. + * + * @param name The cookie's name + * @param value The cookie's value + * @return ApiClient this client + */ + public ApiClient addDefaultCookie(String name, String value) { + if (defaultCookies.containsKey(name)) { + defaultCookies.remove(name); + } + defaultCookies.add(name, value); + return this; + } + + /** + * Get the date format used to parse/format date parameters. + * @return DateFormat format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Parse the given string into Date object. + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Get the ObjectMapper used to make HTTP requests. + * @return ObjectMapper objectMapper + */ + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + /** + * Get the RestClient used to make HTTP requests. + * @return RestClient restClient + */ + public RestClient getRestClient() { + return restClient; + } + + /** + * Format the given parameter object into string. + * @param param the object to convert + * @return String the parameter represented as a String + */ + 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); + } + } + + /** + * Converts a parameter to a {@link MultiValueMap} containing Json-serialized values for use in REST requests + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the Json-serialized String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMapJson(CollectionFormat collectionFormat, String name, Object value) { + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + try { + return parameterToMultiValueMap(collectionFormat, name, objectMapper.writeValueAsString(value)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + List values = new ArrayList<>(); + for(Object o : valueCollection) { + try { + values.add(objectMapper.writeValueAsString(o)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + return parameterToMultiValueMap(collectionFormat, name, "[" + StringUtils.collectionToDelimitedString(values, collectionFormat.separator) + "]"); + } + + /** + * Converts a parameter to a {@link MultiValueMap} for use in REST requests + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMap(CollectionFormat collectionFormat, String name, Object value) { + final MultiValueMap params = new LinkedMultiValueMap<>(); + + if (name == null || name.isEmpty() || value == null) { + return params; + } + + if(collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + if (value instanceof Map) { + @SuppressWarnings("unchecked") + final Map valuesMap = (Map) value; + for (final Entry entry : valuesMap.entrySet()) { + params.add(entry.getKey(), parameterToString(entry.getValue())); + } + return params; + } + + Collection valueCollection = null; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(name, parameterToString(value)); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + if (collectionFormat.equals(CollectionFormat.MULTI)) { + for (Object item : valueCollection) { + params.add(name, parameterToString(item)); + } + return params; + } + + List values = new ArrayList<>(); + for(Object o : valueCollection) { + values.add(parameterToString(o)); + } + params.add(name, collectionFormat.collectionToString(values)); + + return params; + } + + /** + * Check if the given {@code String} is a JSON MIME. + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(String mediaType) { + // "* / *" is default to JSON + if ("*/*".equals(mediaType)) { + return true; + } + + try { + return isJsonMime(MediaType.parseMediaType(mediaType)); + } catch (InvalidMediaTypeException e) { + } + return false; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(MediaType mediaType) { + return mediaType != null && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || mediaType.getSubtype().matches("^.*(\\+json|ndjson)[;]?\\s*$")); + } + + /** + * Check if the given {@code String} is a Problem JSON MIME (RFC-7807). + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents Problem JSON, false otherwise + */ + public boolean isProblemJsonMime(String mediaType) { + return "application/problem+json".equalsIgnoreCase(mediaType); + } + + /** + * 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 List The list of MediaTypes to use for the Accept header + */ + public List selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + MediaType mediaType = MediaType.parseMediaType(accept); + if (isJsonMime(mediaType) && !isProblemJsonMime(accept)) { + return Collections.singletonList(mediaType); + } + } + return MediaType.parseMediaTypes(StringUtils.arrayToCommaDelimitedString(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 MediaType The Content-Type header to use. If the given array is empty, null will be returned. + */ + public MediaType selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return null; + } + for (String contentType : contentTypes) { + MediaType mediaType = MediaType.parseMediaType(contentType); + if (isJsonMime(mediaType)) { + return mediaType; + } + } + return MediaType.parseMediaType(contentTypes[0]); + } + + /** + * Select the body to use for the request + * + * @param obj the body object + * @param formParams the form parameters + * @param contentType the content type of the request + * @return Object the selected body + */ + protected Object selectBody(Object obj, MultiValueMap formParams, MediaType contentType) { + boolean isForm = MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType); + return isForm ? formParams : obj; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param the return type to use + * @param path The sub-path of the HTTP URL + * @param method The request method + * @param pathParams The path parameters + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header 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 The return type into which to deserialize the response + * @return The response body in chosen type + */ + public ResponseSpec invokeAPI(String path, HttpMethod method, Map pathParams, MultiValueMap queryParams, Object body, HttpHeaders headerParams, MultiValueMap cookieParams, MultiValueMap formParams, List accept, MediaType contentType, String[] authNames, ParameterizedTypeReference returnType) throws RestClientException { + final RestClient.RequestBodySpec requestBuilder = prepareRequest(path, method, pathParams, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames); + return requestBuilder.retrieve(); + } + + /** + * Include queryParams in uriParams taking into account the paramName + * @param queryParams The query parameters + * @param uriParams The path parameters + * return templatized query string + */ + protected String generateQueryUri(MultiValueMap queryParams, Map uriParams) { + StringBuilder queryBuilder = new StringBuilder(); + queryParams.forEach((name, values) -> { + if (CollectionUtils.isEmpty(values)) { + if (queryBuilder.length() != 0) { + queryBuilder.append('&'); + } + queryBuilder.append(name); + } else { + int valueItemCounter = 0; + for (Object value : values) { + if (queryBuilder.length() != 0) { + queryBuilder.append('&'); + } + queryBuilder.append(name); + if (value != null) { + String templatizedKey = name + valueItemCounter++; + uriParams.put(templatizedKey, value.toString()); + queryBuilder.append('=').append("{").append(templatizedKey).append("}"); + } + } + } + }); + return queryBuilder.toString(); + } + + protected RestClient.RequestBodySpec prepareRequest(String path, HttpMethod method, Map pathParams, + MultiValueMap queryParams, Object body, HttpHeaders headerParams, + MultiValueMap cookieParams, MultiValueMap formParams, List accept, + MediaType contentType, String[] authNames) { + updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); + + String baseUrl = basePath; + if (serverIndex != null) { + if (serverIndex < 0 || serverIndex >= servers.size()) { + throw new ArrayIndexOutOfBoundsException(String.format( + java.util.Locale.ROOT, + "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() + )); + } + baseUrl = servers.get(serverIndex).URL(serverVariables); + } + + final UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(baseUrl) + .path(path); + + String finalUri = builder.build(false).toUriString(); + Map uriParams = new HashMap<>(); + uriParams.putAll(pathParams); + + if (queryParams != null && !queryParams.isEmpty()) { + //Include queryParams in uriParams taking into account the paramName + String queryUri = generateQueryUri(queryParams, uriParams); + //Append to finalUri the templatized query string like "?param1={param1Value}&....... + finalUri += "?" + queryUri; + } + + final RestClient.RequestBodySpec requestBuilder = restClient.method(method).uri(finalUri, uriParams); + + if (accept != null) { + requestBuilder.accept(accept.toArray(new MediaType[accept.size()])); + } + if(contentType != null) { + requestBuilder.contentType(contentType); + } + + addHeadersToRequest(headerParams, requestBuilder); + addHeadersToRequest(defaultHeaders, requestBuilder); + addCookiesToRequest(cookieParams, requestBuilder); + addCookiesToRequest(defaultCookies, requestBuilder); + + if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) { + formParams.forEach( + (k, v) -> { + if (v instanceof java.util.ArrayList) { + Object o = v.get(0); + if (o != null && o.getClass().getEnumConstants() != null) { + v.set(0, o.toString()); + } + } + }); + } + + var selectedBody = selectBody(body, formParams, contentType); + if (selectedBody != null) { + requestBuilder.body(selectedBody); + } + + return requestBuilder; + } + + /** + * Add headers to the request that is being built + * @param headers The headers to add + * @param requestBuilder The current request + */ + protected void addHeadersToRequest(HttpHeaders headers, RestClient.RequestBodySpec requestBuilder) { + for (Entry> entry : headers.headerSet()) { + List values = entry.getValue(); + for(String value : values) { + if (value != null) { + requestBuilder.header(entry.getKey(), value); + } + } + } + } + + /** + * Add cookies to the request that is being built + * + * @param cookies The cookies to add + * @param requestBuilder The current request + */ + protected void addCookiesToRequest(MultiValueMap cookies, RestClient.RequestBodySpec requestBuilder) { + if (!cookies.isEmpty()) { + requestBuilder.header("Cookie", buildCookieHeader(cookies)); + } + } + + /** + * Build cookie header. Keeps a single value per cookie (as per + * RFC6265 section 5.3). + * + * @param cookies map all cookies + * @return header string for cookies. + */ + protected String buildCookieHeader(MultiValueMap cookies) { + final StringBuilder cookieValue = new StringBuilder(); + String delimiter = ""; + for (final Map.Entry> entry : cookies.entrySet()) { + final String value = entry.getValue().get(entry.getValue().size() - 1); + cookieValue.append(String.format(java.util.Locale.ROOT, "%s%s=%s", delimiter, entry.getKey(), value)); + delimiter = "; "; + } + return cookieValue.toString(); + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams The query parameters + * @param headerParams The header parameters + * @param cookieParams the cookie parameters + */ + protected void updateParamsForAuth(String[] authNames, MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + throw new RestClientException("Authentication undefined: " + authName); + } + auth.applyToParams(queryParams, headerParams, cookieParams); + } + } + + /** + * Formats the specified collection path parameter to a string value. + * + * @param collectionFormat The collection format of the parameter. + * @param values The values of the parameter. + * @return String representation of the parameter + */ + public String collectionPathParameterToString(CollectionFormat collectionFormat, Collection values) { + // create the value based on the collection format + if (CollectionFormat.MULTI.equals(collectionFormat)) { + // not valid for path params + return parameterToString(values); + } + + // collectionFormat is assumed to be "csv" by default + if(collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + return collectionFormat.collectionToString(values); + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/README.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/README.mustache new file mode 100644 index 000000000..ec6244a82 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/README.mustache @@ -0,0 +1,216 @@ +# {{artifactId}} + +{{appName}} + +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +- Generator version: {{generatorVersion}} + +{{{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 17+ +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 + 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` + +## 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 = new ApiClient(); + 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 (HttpStatusCodeException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getStatusCode().value()); + System.err.println("Reason: " + e.getResponseBodyAsString()); + 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/boat-scaffold/src/main/templates/boat-java/libraries/restclient/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/additional_properties.mustache new file mode 100644 index 000000000..8e7182792 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/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/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api.mustache new file mode 100644 index 000000000..30b8b1329 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api.mustache @@ -0,0 +1,221 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; + +{{#imports}}import {{import}}; +{{/imports}} + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Arrays; +import java.util.stream.Collectors; + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} +{{#generateClientAsBean}} +import org.springframework.beans.factory.annotation.Autowired; +{{/generateClientAsBean}} +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +{{#generateClientAsBean}} +import org.springframework.stereotype.Component; +{{/generateClientAsBean}} +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient.ResponseSpec; +import org.springframework.web.client.RestClientResponseException; + +{{>generatedAnnotation}} + +{{#generateClientAsBean}} +@Component("{{package}}.{{classname}}") +{{/generateClientAsBean}} +{{#operations}} +public class {{classname}} { + private ApiClient apiClient; + + public {{classname}}() { + this(new ApiClient()); + } + +{{#generateClientAsBean}} + @Autowired +{{/generateClientAsBean}} + public {{classname}}(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public ApiClient getApiClient() { + return apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + {{#operation}} +{{#singleRequestParameter}}{{>single_request_parameter}}{{/singleRequestParameter}}{{^singleRequestParameter}}{{/singleRequestParameter}} + /** + * {{summary}} + * {{notes}} +{{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} +{{/responses}}{{#allParams}} * @param {{paramName}} {{description}}{{^description}}The {{paramName}} parameter{{/description}} +{{/allParams}}{{#returnType}} * @return {{.}} +{{/returnType}} * @throws RestClientResponseException if an error occurs while attempting to invoke the API +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} +{{#isDeprecated}} + * @deprecated +{{/isDeprecated}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + private ResponseSpec {{operationId}}RequestCreation({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws RestClientResponseException { + Object postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#allParams}} + {{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new RestClientResponseException("Missing the required parameter '{{paramName}}' when calling {{operationId}}", HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), null, null, null); + } + {{/required}} + {{/allParams}} + // create path and map variables + final Map pathParams = new HashMap<>(); + {{#hasPathParams}} + + {{#pathParams}} + pathParams.put("{{baseName}}", {{#collectionFormat}}apiClient.collectionPathParameterToString(ApiClient.CollectionFormat.valueOf("csv".toUpperCase()), {{/collectionFormat}}{{{paramName}}}{{#collectionFormat}}){{/collectionFormat}}); + {{/pathParams}} + {{/hasPathParams}} + + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + final HttpHeaders headerParams = new HttpHeaders(); + final MultiValueMap cookieParams = new LinkedMultiValueMap<>(); + final MultiValueMap formParams = new LinkedMultiValueMap<>(); + {{#hasQueryParams}} + + {{#queryParams}} + {{#queryIsJsonMimeType}} + queryParams.putAll(apiClient.parameterToMultiValueMapJson({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/queryIsJsonMimeType}} + {{^queryIsJsonMimeType}} + {{#isExplode}} + {{#hasVars}} + {{#vars}} + queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}}.{{getter}}())); + {{/vars}} + {{/hasVars}} + {{^hasVars}} + queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/hasVars}} + {{/isExplode}} + {{^isExplode}} + queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/isExplode}} + {{/queryIsJsonMimeType}} + {{/queryParams}} + {{/hasQueryParams}} + {{#hasHeaderParams}} + + {{#headerParams}} + if ({{paramName}} != null) + headerParams.add("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^-last}} + {{/-last}} + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasCookieParams}} + + {{#cookieParams}} + cookieParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/cookieParams}} + {{/hasCookieParams}} + {{#hasFormParams}} + + {{#formParams}} + if ({{paramName}} != null) + formParams.add{{#collectionFormat}}All{{/collectionFormat}}("{{baseName}}", {{#isFile}}{{^collectionFormat}}{{#useAbstractionForFiles}}{{paramName}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}new FileSystemResource({{paramName}}){{/useAbstractionForFiles}}{{/collectionFormat}}{{/isFile}}{{#isFile}}{{#collectionFormat}}{{paramName}}.stream(){{^useAbstractionForFiles}}.map(FileSystemResource::new){{/useAbstractionForFiles}}.collect(Collectors.toList()){{/collectionFormat}}{{/isFile}}{{^isFile}}{{paramName}}{{/isFile}}); + {{/formParams}} + {{/hasFormParams}} + + final String[] localVarAccepts = { {{#hasProduces}} + {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} + {{/hasProduces}}}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = { {{#hasConsumes}} + {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} + {{/hasConsumes}}}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; + + {{#returnType}}ParameterizedTypeReference<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}> localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}} + return apiClient.invokeAPI("{{{path}}}", HttpMethod.{{httpMethod}}, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType); + } + + /** + * {{summary}} + * {{notes}} +{{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} +{{/responses}}{{#allParams}} * @param {{paramName}} {{description}}{{^description}}The {{paramName}} parameter{{/description}} +{{/allParams}}{{#returnType}} * @return {{.}} +{{/returnType}} * @throws RestClientResponseException if an error occurs while attempting to invoke the API +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + public {{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws RestClientResponseException { + {{#returnType}}ParameterizedTypeReference<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}> localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}} + {{#returnType}}return {{/returnType}}{{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).body(localVarReturnType); + } + + /** + * {{summary}} + * {{notes}} +{{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} +{{/responses}}{{#allParams}} * @param {{paramName}} {{description}}{{^description}}The {{paramName}} parameter{{/description}} +{{/allParams}}{{#returnType}} * @return ResponseEntity<{{.}}> +{{/returnType}} * @throws RestClientResponseException if an error occurs while attempting to invoke the API +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + public {{#returnType}}ResponseEntity<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}ResponseEntity{{/returnType}} {{operationId}}WithHttpInfo({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws RestClientResponseException { + {{#returnType}}ParameterizedTypeReference<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}> localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference<>() {};{{/returnType}} + return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).toEntity(localVarReturnType); + } + + /** + * {{summary}} + * {{notes}} +{{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} +{{/responses}}{{#allParams}} * @param {{paramName}} {{description}}{{^description}}The {{paramName}} parameter{{/description}} +{{/allParams}} + * @return ResponseSpec + * @throws RestClientResponseException if an error occurs while attempting to invoke the API +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + public ResponseSpec {{operationId}}WithResponseSpec({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws RestClientResponseException { + return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + } + {{/operation}} +} +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api_test.mustache new file mode 100644 index 000000000..e54a4ccc2 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/api_test.mustache @@ -0,0 +1,45 @@ +{{>licenseInfo}} + +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} +/** + * API tests for {{classname}} + */ +@Disabled +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}}{{#operation}} + /** + * {{summary}} + * + * {{notes}} + */ + @Test + public void {{operationId}}Test() { + {{#allParams}} + {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}} = null; + {{/allParams}} + {{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{.}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{.}}}{{/isResponseFile}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + + // TODO: test validations + } + {{/operation}}{{/operations}} +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/ApiKeyAuth.mustache new file mode 100644 index 000000000..a5662c4a7 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/ApiKeyAuth.mustache @@ -0,0 +1,65 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>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(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if (location.equals("query")) { + queryParams.add(paramName, value); + } else if (location.equals("header")) { + headerParams.add(paramName, value); + } else if (location.equals("cookie")) { + cookieParams.add(paramName, value); + } + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/Authentication.mustache new file mode 100644 index 000000000..70b1d1054 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/Authentication.mustache @@ -0,0 +1,19 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>generatedAnnotation}} + +public interface Authentication { + /** + * Apply authentication settings to header and / or query parameters. + * + * @param queryParams The query parameters for the request + * @param headerParams The header parameters for the request + * @param cookieParams The cookie parameters for the request + */ + void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams); +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBasicAuth.mustache new file mode 100644 index 000000000..4ee900963 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBasicAuth.mustache @@ -0,0 +1,41 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>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(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + if (username == null && password == null) { + return; + } + String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); + headerParams.add(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBearerAuth.mustache new file mode 100644 index 000000000..93fe8732a --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/HttpBearerAuth.mustache @@ -0,0 +1,59 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import java.util.Optional; +import java.util.function.Supplier; +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>generatedAnnotation}} + +public class HttpBearerAuth implements Authentication { + private final String scheme; + private Supplier tokenSupplier; + + 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 tokenSupplier.get(); + } + + /** + * 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.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; + } + + @Override + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + String bearerToken = Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); + if (bearerToken == null) { + return; + } + headerParams.add(HttpHeaders.AUTHORIZATION, (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); + } + + private static String upperCaseBearer(String scheme) { + return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuth.mustache new file mode 100644 index 000000000..55b5da8eb --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuth.mustache @@ -0,0 +1,51 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import java.util.Optional; +import java.util.function.Supplier; +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +/** + * Provides support for RFC 6750 - Bearer Token usage for OAUTH 2.0 Authorization. + */ +{{>generatedAnnotation}} + +public class OAuth implements Authentication { + private Supplier tokenSupplier; + + /** + * Returns the bearer token used for Authorization. + * + * @return The bearer token + */ + public String getAccessToken() { + return tokenSupplier.get(); + } + + /** + * Sets the bearer access token used for Authorization. + * + * @param accessToken The bearer token to send in the Authorization header + */ + public void setAccessToken(String accessToken) { + setAccessToken(() -> accessToken); + } + + /** + * Sets the supplier of bearer tokens used for Authorization. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setAccessToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; + } + + @Override + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + Optional.ofNullable(tokenSupplier).map(Supplier::get).ifPresent(accessToken -> + headerParams.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ); + } +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuthFlow.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuthFlow.mustache new file mode 100644 index 000000000..759f354f5 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/auth/OAuthFlow.mustache @@ -0,0 +1,7 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +public enum OAuthFlow { + accessCode, implicit, password, application +} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/build.gradle.mustache new file mode 100644 index 000000000..b2e7066f1 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/build.gradle.mustache @@ -0,0 +1,159 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + } +} + +repositories { + mavenCentral() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 23 + buildToolsVersion '23.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 22 + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + archiveClassifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven-publish' + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + publishing { + publications { + maven(MavenPublication) { + artifactId = '{{artifactId}}' + from components.java + } + } + } + + task execute(type:JavaExec) { + mainClass = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#swagger1AnnotationLibrary}} + swagger_annotations_version = "1.6.9" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" + {{#openApiNullable}} + jackson_databind_nullable_version = "0.2.9" + {{/openApiNullable}} + spring_web_version = "6.1.21" + jakarta_annotation_version = "2.1.1" + jodatime_version = "2.9.9" + junit_version = "5.10.2" +} + +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 "org.springframework:spring-web:$spring_web_version" + implementation "org.springframework:spring-context:$spring_web_version" + 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_databind_version" + implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version" + {{#openApiNullable}} + implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" + {{/openApiNullable}} + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{#joda}} + implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + implementation "joda-time:joda-time:$jodatime_version" + {{/joda}} + {{#withXml}} + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jackson_version" + {{/withXml}} + implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" +} + +test { + // Enable JUnit 5 (Gradle 4.6+). + useJUnitPlatform() + + // Always run tests, even when nothing changed. + dependsOn 'cleanTest' + + // Show test results. + testLogging { + events "passed", "skipped", "failed" + } + +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/model.mustache new file mode 100644 index 000000000..108748f60 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/model.mustache @@ -0,0 +1,78 @@ +{{>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}} +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/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pojo.mustache new file mode 100644 index 000000000..d72fdfee1 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pojo.mustache @@ -0,0 +1,630 @@ +/** + * {{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 {{>sealed}}class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{{>permits}}{ +{{#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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{^isXmlAttribute}} + {{#isDateTime}} + @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) + {{/isDateTime}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{/gson}} + {{^isDiscriminator}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{/isDiscriminator}} + {{#isDiscriminator}} + // The discriminator does not have Nullability-annotation since it is added during serialization by the @JsonTypeName annotation + {{/isDiscriminator}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{.}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} 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}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{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}} + /** + * Constructor with only readonly parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} + */ + {{#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}} +{{#vendorExtensions.x-java-all-args-constructor}} + + /** + * Constructor with all args parameters + */ + public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { +{{#parent}} + super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); +{{/parent}} + {{#vars}} + 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}}; +{{/vars}} + } +{{/vendorExtensions.x-java-all-args-constructor}} + +{{#vars}} + {{^isReadOnly}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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{{nameInPascalCase}}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{{nameInPascalCase}}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}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#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}}({{>nullable_var_annotations}} {{{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}} +{{>libraries/restclient/additional_properties}} + + {{#parent}} + {{#readWriteVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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}} + {{/readWriteVars}} + {{/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}}{{#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({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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{{#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.datatypeWithEnum}}} _item : {{getter}}()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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}} +{{#generateBuilders}} + + {{>javaBuilder}}{{! prevent indent}} +{{/generateBuilders}} + +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pom.mustache new file mode 100644 index 000000000..09d14ec97 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/pom.mustache @@ -0,0 +1,365 @@ + + 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-enforcer-plugin + 3.4.0 + + + enforce-maven + + enforce + + + + + 2.2.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + false + true + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-version} + + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + 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-compiler-plugin + 3.11.0 + + 17 + 17 + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + 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 + + + + + org.springframework + spring-web + ${spring-web-version} + + + org.springframework + spring-context + ${spring-web-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.jakarta.rs + jackson-jakarta-rs-json-provider + ${jackson-version} + + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable-version} + + {{/openApiNullable}} + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + io.github.threeten-jaxb + threeten-jaxb-core + 1.2 + + {{/withXml}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + + joda-time + joda-time + ${jodatime-version} + + {{/joda}} + {{#useBeanValidation}} + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + + {{/useBeanValidation}} + {{#performBeanValidation}} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator-version} + + {{/performBeanValidation}} + + jakarta.annotation + jakarta.annotation-api + ${jakarta-annotation-version} + provided + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-version} + test + + + + UTF-8 + {{#swagger1AnnotationLibrary}} + 1.6.9 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 6.1.21 + 2.19.2 + 2.19.2 + {{#openApiNullable}} + 0.2.9 + {{/openApiNullable}} + 2.1.1 + {{#joda}} + 2.9.9 + {{/joda}} + 3.0.2 + {{#performBeanValidation}} + 8.0.3.Final + {{/performBeanValidation}} + 5.10.2 + + diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/restclient/single_request_parameter.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/single_request_parameter.mustache new file mode 100644 index 000000000..94ee10c49 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/restclient/single_request_parameter.mustache @@ -0,0 +1,110 @@ +{{#hasParams}} +{{^hasSingleParam}} + + {{^staticRequest}} + public record {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){} + {{/staticRequest}} + {{#staticRequest}} + public static class {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request { + {{#allParams}} + private {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}; + {{/allParams}} + + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request() {} + + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) { + {{#allParams}} + this.{{paramName}} = {{paramName}}; + {{/allParams}} + } + + {{#allParams}} + public {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}() { + return this.{{paramName}}; + } + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request {{paramName}}({{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}) { + this.{{paramName}} = {{paramName}}; + return this; + } + + {{/allParams}} + @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; + }{{#hasParams}} + {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request request = ({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request) o; + return {{#allParams}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{paramName}}, request.{{paramName}}()){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{paramName}}, request.{{paramName}}()){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && + {{/-last}}{{/allParams}};{{/hasParams}}{{^hasParams}} + return true;{{/hasParams}} + {{/useReflectionEqualsHashCode}} + } + + @Override + public int hashCode() { + {{#useReflectionEqualsHashCode}} + return HashCodeBuilder.reflectionHashCode(this); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + return Objects.hash({{#allParams}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{paramName}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{paramName}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{paramName}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/useReflectionEqualsHashCode}} + } + } + {{/staticRequest}} + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + {{#returnType}} * @return {{.}} + {{/returnType}} * @throws RestClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws RestClientResponseException { + {{#returnType}}return {{/returnType}}this.{{operationId}}({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + {{#returnType}} * @return ResponseEntity<{{.}}> + {{/returnType}} * @throws RestClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#returnType}}ResponseEntity<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}ResponseEntity{{/returnType}} {{operationId}}WithHttpInfo({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws RestClientResponseException { + return this.{{operationId}}WithHttpInfo({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + * @return ResponseSpec + * @throws RestClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public ResponseSpec {{operationId}}WithResponseSpec({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws RestClientResponseException { + return this.{{operationId}}WithResponseSpec({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } +{{/hasSingleParam}} +{{/hasParams}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/ApiClient.mustache index fe7575723..7a3b64e31 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.io.File; @@ -27,16 +29,17 @@ import java.util.regex.Pattern; import java.time.OffsetDateTime; {{/jsr310}} -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Form; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; +import {{javaxPackage}}.ws.rs.client.Client; +import {{javaxPackage}}.ws.rs.client.ClientBuilder; +import {{javaxPackage}}.ws.rs.client.Entity; +import {{javaxPackage}}.ws.rs.client.Invocation; +import {{javaxPackage}}.ws.rs.client.WebTarget; +import {{javaxPackage}}.ws.rs.core.Form; +import {{javaxPackage}}.ws.rs.core.GenericEntity; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.MediaType; +import {{javaxPackage}}.ws.rs.core.Response; +import {{javaxPackage}}.ws.rs.core.Response.Status; import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.internal.ClientConfiguration; @@ -52,22 +55,23 @@ 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}}}"; - private boolean debugging = false; + protected Map defaultHeaderMap = new HashMap(); + protected Map defaultCookieMap = new HashMap(); + protected String basePath = "{{{basePath}}}"; + protected boolean debugging = false; - private Client httpClient; - private JSON json; - private String tempFolderPath = null; + protected Client httpClient; + protected JSON json; + protected String tempFolderPath = null; - private Map authentications; + protected Map authentications; - private int statusCode; - private Map> responseHeaders; + protected int statusCode; + protected Map> responseHeaders; - private DateFormat dateFormat; + protected DateFormat dateFormat; public ApiClient() { json = new JSON(); @@ -85,8 +89,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. @@ -497,15 +501,16 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { if (param.getValue() instanceof File) { File file = (File) param.getValue(); try { - multipart.addFormData(param.getValue().toString(),new FileInputStream(file),MediaType.APPLICATION_OCTET_STREAM_TYPE); + multipart.addFormData(param.getKey(),new FileInputStream(file),MediaType.APPLICATION_OCTET_STREAM_TYPE, file.getName()); } catch (FileNotFoundException e) { throw new ApiException("Could not serialize multipart/form-data "+e.getMessage()); } } else { - multipart.addFormData(param.getValue().toString(),param.getValue().toString(),MediaType.APPLICATION_OCTET_STREAM_TYPE); + multipart.addFormData(param.getKey(),param.getValue().toString(),MediaType.APPLICATION_OCTET_STREAM_TYPE); } } - entity = Entity.entity(multipart.getFormData(), MediaType.MULTIPART_FORM_DATA_TYPE); + GenericEntity genericEntity = new GenericEntity(multipart) { }; + entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE); } else if (contentType.startsWith("application/x-www-form-urlencoded")) { Form form = new Form(); for (Entry param: formParams.entrySet()) { @@ -703,7 +708,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - private Response invoke(Invocation.Builder invocationBuilder, String method, Entity entity) throws ApiException { + protected Response invoke(Invocation.Builder invocationBuilder, String method, Entity entity) throws ApiException { Response response = null; if ("GET".equals(method)) { @@ -732,7 +737,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { /** * Build the Client used to make HTTP requests. */ - private Client buildHttpClient(boolean debugging) { + protected Client buildHttpClient(boolean debugging) { final ClientConfiguration clientConfig = new ClientConfiguration(ResteasyProviderFactory.getInstance()); clientConfig.register(json); if(debugging){ @@ -740,7 +745,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } return ClientBuilder.newClient(clientConfig); } - private Map> buildResponseHeaders(Response response) { + protected Map> buildResponseHeaders(Response response) { Map> responseHeaders = new HashMap>(); for (Entry> entry: response.getHeaders().entrySet()) { List values = entry.getValue(); @@ -758,7 +763,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * * @param authNames The authentications to apply */ - private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { + protected 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); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/JSON.mustache index 71bd624f7..5094d1b81 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/JSON.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import com.fasterxml.jackson.annotation.*; @@ -9,16 +11,17 @@ import com.fasterxml.jackson.datatype.jsr310.*; import java.text.DateFormat; -import javax.ws.rs.ext.ContextResolver; +import {{javaxPackage}}.ws.rs.ext.ContextResolver; {{>generatedAnnotation}} + public class JSON implements ContextResolver { private ObjectMapper mapper; public JSON() { mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/api.mustache index 9194b7541..8d8dd28cd 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/api.mustache @@ -5,19 +5,18 @@ import {{invokerPackage}}.ApiClient; import {{invokerPackage}}.Configuration; import {{invokerPackage}}.Pair; -import javax.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.core.GenericType; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { private ApiClient apiClient; @@ -56,7 +55,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { Object localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; {{#allParams}}{{#required}} // verify the required parameter '{{paramName}}' is set @@ -69,10 +68,10 @@ public class {{classname}} { .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; // query params - {{javaUtilPrefix}}List localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); - {{javaUtilPrefix}}Map localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarCookieParams = new {{javaUtilPrefix}}HashMap(); - {{javaUtilPrefix}}Map localVarFormParams = new {{javaUtilPrefix}}HashMap(); + List localVarQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); {{#queryParams}} localVarQueryParams.addAll(apiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}})); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.gradle.mustache index 93a8b84ff..d6b881602 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -98,15 +98,16 @@ if(hasProperty('target') && target == 'android') { ext { swagger_annotations_version = "1.6.3" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" threetenbp_version = "2.9.10" resteasy_version = "4.5.11.Final" - junit_version = "4.13" + assertj_version = "3.23.1" + junit_version = "5.10.2" } dependencies { @@ -124,5 +125,6 @@ dependencies { {{/openApiNullable}} implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + testImplementation "org.assertj:assertj-core:$assertj_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.sbt.mustache index 3e1de7399..3b2b1086a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/build.sbt.mustache @@ -3,23 +3,24 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( "io.swagger" % "swagger-annotations" % "1.5.22" % "compile", "org.jboss.resteasy" % "resteasy-client" % "3.1.3.Final" % "compile", "org.jboss.resteasy" % "resteasy-multipart-provider" % "4.5.11.Final" % "compile", "org.jboss.resteasy" % "resteasy-jackson2-provider" % "4.5.11.Final" % "compile", - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.2" % "compile", - "com.github.joschi.jackson" % "jackson-datatype-threetenbp" % "2.9.10" % "compile", - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.10" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1" % "compile", + "com.github.joschi.jackson" % "jackson-datatype-threetenbp" % "2.15.2" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.17.1" % "compile", "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", - "junit" % "junit" % "4.13" % "test", + "org.assertj" % "assertj-core" % "3.23.1" % "test", + "junit" % "junit" % "5.10.2" % "test", "com.novocode" % "junit-interface" % "0.10" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/pom.mustache index 1fbbb1d61..66314a6e2 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resteasy/pom.mustache @@ -61,17 +61,16 @@ org.apache.maven.plugins maven-surefire-plugin - 2.12 + 2.22.2 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods - pertest @@ -173,6 +172,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} com.google.code.findbugs @@ -271,24 +277,41 @@ - junit - junit + org.assertj + assertj-core + ${assertj-version} + test + + + org.junit.jupiter + junit-jupiter-api ${junit-version} test UTF-8 + {{#swagger1AnnotationLibrary}} 1.6.6 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} 4.7.6.Final - 2.13.4 - 2.13.4.2 + 2.19.2 + 2.19.2 {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} + {{#useJakartaEe}} + 2.1.1 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 + {{/useJakartaEe}} 2.9.10 1.0.0 - 4.13 + 3.27.7 + 5.10.2 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache index 27a976ded..09a7294b0 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; {{#withXml}} @@ -7,13 +9,11 @@ import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; -{{#restTemplateBeanName}} -import org.springframework.beans.factory.annotation.Qualifier; -{{/restTemplateBeanName}} import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; @@ -28,13 +28,13 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; {{/withXml}} -{{#createApiComponent}} import org.springframework.stereotype.Component; -{{/createApiComponent}} import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -66,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; +import java.util.function.Supplier; {{#jsr310}} import java.time.OffsetDateTime; {{/jsr310}} @@ -85,44 +86,51 @@ import {{invokerPackage}}.auth.OAuth; {{/hasOAuthMethods}} {{>generatedAnnotation}} -{{#createApiComponent}} + +{{#generateClientAsBean}} @Component("{{invokerPackage}}.ApiClient") -{{/createApiComponent}} +{{/generateClientAsBean}} public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { public enum CollectionFormat { CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null); - private final String separator; + protected final String separator; - private CollectionFormat(String separator) { + CollectionFormat(String separator) { this.separator = separator; } - private String collectionToString(Collection collection) { + protected String collectionToString(Collection collection) { return StringUtils.collectionToDelimitedString(collection, separator); } } - private boolean debugging = false; + protected boolean debugging = false; + + protected HttpHeaders defaultHeaders = new HttpHeaders(); + protected MultiValueMap defaultCookies = new LinkedMultiValueMap(); + + protected int maxAttemptsForRetry = {{maxAttemptsForRetry}}; - private HttpHeaders defaultHeaders = new HttpHeaders(); - private MultiValueMap defaultCookies = new LinkedMultiValueMap(); + protected long waitTimeMillis = {{waitTimeMillis}}; - private String basePath = "{{basePath}}"; + protected String basePath = "{{basePath}}"; - private RestTemplate restTemplate; + protected RestTemplate restTemplate; - private Map authentications; + protected Map authentications; - private DateFormat dateFormat; + protected DateFormat dateFormat; public ApiClient() { this.restTemplate = buildRestTemplate(); init(); } + {{#generateClientAsBean}} @Autowired - public ApiClient({{#restTemplateBeanName}}@Qualifier("{{restTemplateBeanName}}") {{/restTemplateBeanName}}RestTemplate restTemplate) { + {{/generateClientAsBean}} + public ApiClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; init(); } @@ -136,12 +144,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // Set default User-Agent. - setUserAgent("Java-SDK"); + 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}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. @@ -168,6 +176,46 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return this; } + /** + * Get the max attempts for retry + * + * @return int the max attempts + */ + public int getMaxAttemptsForRetry() { + return maxAttemptsForRetry; + } + + /** + * Set the max attempts for retry + * + * @param maxAttemptsForRetry the max attempts for retry + * @return ApiClient this client + */ + public ApiClient setMaxAttemptsForRetry(int maxAttemptsForRetry) { + this.maxAttemptsForRetry = maxAttemptsForRetry; + return this; + } + + /** + * Get the wait time in milliseconds + * + * @return long wait time in milliseconds + */ + public long getWaitTimeMillis() { + return waitTimeMillis; + } + + /** + * Set the wait time in milliseconds + * + * @param waitTimeMillis the wait time in milliseconds + * @return ApiClient this client + */ + public ApiClient setWaitTimeMillis(long waitTimeMillis) { + this.waitTimeMillis = waitTimeMillis; + return this; + } + /** * Get authentications (key: authentication name, value: authentication). * @@ -189,14 +237,23 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { {{#hasHttpBearerMethods}} /** - * Helper method to set token for HTTP bearer authentication. + * Helper method to set access token for the first Bearer authentication. * - * @param bearerToken the token + * @param bearerToken Bearer token */ public void setBearerToken(String bearerToken) { + setBearerToken(() -> bearerToken); + } + + /** + * Helper method to set the supplier of access tokens for Bearer authentication. + * + * @param tokenSupplier The supplier of bearer tokens + */ + public void setBearerToken(Supplier tokenSupplier) { for (Authentication auth : authentications.values()) { if (auth instanceof HttpBearerAuth) { - ((HttpBearerAuth) auth).setBearerToken(bearerToken); + ((HttpBearerAuth) auth).setBearerToken(tokenSupplier); return; } } @@ -277,9 +334,18 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param accessToken Access token */ public void setAccessToken(String accessToken) { + setAccessToken(() -> accessToken); + } + + /** + * Helper method to set the supplier of access tokens for OAuth2 authentication. + * + * @param tokenSupplier The supplier of bearer tokens + */ + public void setAccessToken(Supplier tokenSupplier) { for (Authentication auth : authentications.values()) { if (auth instanceof OAuth) { - ((OAuth) auth).setAccessToken(accessToken); + ((OAuth) auth).setAccessToken(tokenSupplier); return; } } @@ -307,10 +373,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return ApiClient this client */ public ApiClient addDefaultHeader(String name, String value) { - if (defaultHeaders.containsKey(name)) { - defaultHeaders.remove(name); - } - defaultHeaders.add(name, value); + defaultHeaders.set(name, value); return this; } @@ -649,7 +712,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + } }); return queryBuilder.toString(); @@ -677,7 +740,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { public ResponseEntity invokeAPI(String path, HttpMethod method, Map pathParams, MultiValueMap queryParams, Object body, HttpHeaders headerParams, MultiValueMap cookieParams, MultiValueMap formParams, List accept, MediaType contentType, String[] authNames, ParameterizedTypeReference returnType) throws RestClientException { updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); - Map uriParams = new HashMap<>(pathParams); + Map uriParams = new HashMap<>(); + uriParams.putAll(pathParams); String finalUri = path; @@ -688,7 +752,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { finalUri += "?" + queryUri; } String expandedPath = this.expandPath(finalUri, uriParams); - final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(expandedPath); + final UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(basePath).path(expandedPath); URI uri; try { @@ -697,7 +761,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { throw new RestClientException("Could not build URL: " + builder.toUriString(), ex); } - final BodyBuilder requestBuilder = RequestEntity.method(method, UriComponentsBuilder.fromHttpUrl(basePath).toUriString() + finalUri, uriParams); + final BodyBuilder requestBuilder = RequestEntity.method(method, UriComponentsBuilder.fromUriString(basePath).toUriString() + finalUri, uriParams); if (accept != null) { requestBuilder.accept(accept.toArray(new MediaType[accept.size()])); } @@ -712,7 +776,34 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { RequestEntity requestEntity = requestBuilder.body(selectBody(body, formParams, contentType)); - ResponseEntity responseEntity = restTemplate.exchange(requestEntity, returnType); + ResponseEntity responseEntity = null; + int attempts = 0; + while (attempts < maxAttemptsForRetry) { + try { + responseEntity = restTemplate.exchange(requestEntity, returnType); + break; + } catch (HttpServerErrorException | HttpClientErrorException ex) { + if (ex instanceof HttpServerErrorException + || ex.getStatusCode().equals(HttpStatus.TOO_MANY_REQUESTS)) { + attempts++; + if (attempts < maxAttemptsForRetry) { + try { + Thread.sleep(waitTimeMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + throw ex; + } + } else { + throw ex; + } + } + } + + if (responseEntity == null) { + throw new RestClientException("ResponseEntity is null"); + } if (responseEntity.getStatusCode().is2xxSuccessful()) { return responseEntity; @@ -728,7 +819,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param requestBuilder The current request */ protected void addHeadersToRequest(HttpHeaders headers, BodyBuilder requestBuilder) { + {{#useJakartaEe}} + for (Entry> entry : headers.headerSet()) { + {{/useJakartaEe}} + {{^useJakartaEe}} for (Entry> entry : headers.entrySet()) { + {{/useJakartaEe}} List values = entry.getValue(); for (String value : values) { if (value != null) { @@ -757,12 +853,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param cookies map all cookies * @return header string for cookies. */ - private String buildCookieHeader(MultiValueMap cookies) { + protected String buildCookieHeader(MultiValueMap cookies) { final StringBuilder cookieValue = new StringBuilder(); String delimiter = ""; for (final Map.Entry> entry : cookies.entrySet()) { final String value = entry.getValue().get(entry.getValue().size() - 1); - cookieValue.append(String.format("%s%s=%s", delimiter, entry.getKey(), value)); + cookieValue.append(String.format(java.util.Locale.ROOT, "%s%s=%s", delimiter, entry.getKey(), value)); delimiter = "; "; } return cookieValue.toString(); @@ -811,8 +907,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - private static class ApiClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { - private final Log log = LogFactory.getLog(ApiClientHttpRequestInterceptor.class); + protected class ApiClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + protected final Log log = LogFactory.getLog(ApiClientHttpRequestInterceptor.class); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { @@ -822,26 +918,31 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return response; } - private void logRequest(HttpRequest request, byte[] body) { + protected void logRequest(HttpRequest request, byte[] body) throws UnsupportedEncodingException { log.info("URI: " + request.getURI()); log.info("HTTP Method: " + request.getMethod()); log.info("HTTP Headers: " + headersToString(request.getHeaders())); log.info("Request Body: " + new String(body, StandardCharsets.UTF_8)); } - private void logResponse(ClientHttpResponse response) throws IOException { + protected void logResponse(ClientHttpResponse response) throws IOException { log.info("HTTP Status Code: " + response.getStatusCode().value()); log.info("Status Text: " + response.getStatusText()); log.info("HTTP Headers: " + headersToString(response.getHeaders())); log.info("Response Body: " + bodyToString(response.getBody())); } - private String headersToString(HttpHeaders headers) { - if (headers == null || headers.isEmpty()) { + protected String headersToString(HttpHeaders headers) { + if(headers == null || headers.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(); + {{#useJakartaEe}} + for (Entry> entry : headers.headerSet()) { + {{/useJakartaEe}} + {{^useJakartaEe}} for (Entry> entry : headers.entrySet()) { + {{/useJakartaEe}} builder.append(entry.getKey()).append("=["); for (String value : entry.getValue()) { builder.append(value).append(","); @@ -853,7 +954,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return builder.toString(); } - private String bodyToString(InputStream body) throws IOException { + protected String bodyToString(InputStream body) throws IOException { StringBuilder builder = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, StandardCharsets.UTF_8)); String line = bufferedReader.readLine(); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/BaseApi.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/BaseApi.mustache new file mode 100644 index 000000000..fe54fcf7b --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/BaseApi.mustache @@ -0,0 +1,77 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import org.springframework.web.client.RestClientException; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; + +{{>generatedAnnotation}} + +public abstract class BaseApi { + + protected ApiClient apiClient; + + public BaseApi() { + this(new ApiClient()); + } + + public BaseApi(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public ApiClient getApiClient() { + return apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @return ResponseEntity<Void> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI(String url, HttpMethod method) throws RestClientException { + return invokeAPI(url, method, null, new ParameterizedTypeReference() {}); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @return ResponseEntity<Void> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI(String url, HttpMethod method, Object request) throws RestClientException { + return invokeAPI(url, method, request, new ParameterizedTypeReference() {}); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param returnType The return type. + * @return ResponseEntity in the specified type. + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI(String url, HttpMethod method, ParameterizedTypeReference returnType) throws RestClientException { + return invokeAPI(url, method, null, returnType); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for subsequent requests. + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @param returnType The return type. + * @return ResponseEntity in the specified type. + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public abstract ResponseEntity invokeAPI(String url, HttpMethod method, Object request, ParameterizedTypeReference returnType) throws RestClientException; +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/additional_properties.mustache new file mode 100644 index 000000000..8e7182792 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/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/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api.mustache index 95ebe98d7..eb0f4b50c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api.mustache @@ -1,26 +1,25 @@ package {{package}}; import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.BaseApi; {{#imports}}import {{import}}; {{/imports}} -{{#useBeanValidation}} -import jakarta.validation.constraints.*; -import jakarta.validation.Valid; -{{/useBeanValidation}} - -{{^fullJavaUtil}}import java.util.Collections; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors;{{/fullJavaUtil}} +import java.util.stream.Collectors; + +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} import org.springframework.beans.factory.annotation.Autowired; -{{#createApiComponent}} import org.springframework.stereotype.Component; -{{/createApiComponent}} import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClientException; @@ -34,28 +33,22 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; {{>generatedAnnotation}} -{{#createApiComponent}} + +{{#generateClientAsBean}} @Component("{{package}}.{{classname}}") -{{/createApiComponent}} +{{/generateClientAsBean}} {{#operations}} -public class {{classname}} { - private ApiClient apiClient; +public class {{classname}} extends BaseApi { public {{classname}}() { - this(new ApiClient()); + super(new ApiClient()); } + {{#generateClientAsBean}} @Autowired + {{/generateClientAsBean}} public {{classname}}(ApiClient apiClient) { - this.apiClient = apiClient; - } - - public ApiClient getApiClient() { - return apiClient; - } - - public void setApiClient(ApiClient apiClient) { - this.apiClient = apiClient; + super(apiClient); } {{#operation}} @@ -131,12 +124,12 @@ public class {{classname}} { final MultiValueMap localVarCookieParams = new LinkedMultiValueMap(); final MultiValueMap localVarFormParams = new LinkedMultiValueMap();{{#hasQueryParams}} - {{#queryParams}}{{#isExplode}}{{#hasVars}} - if ({{paramName}} != null) { + {{#queryParams}}{{#isExplode}}{{#hasVars}} + if ({{paramName}} != null) { {{#vars}} localVarQueryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}}.{{getter}}())); {{/vars}}}{{/hasVars}}{{^hasVars}}localVarQueryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); - {{/hasVars}}{{/isExplode}}{{^isExplode}}localVarQueryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); - {{/isExplode}}{{/queryParams}}{{/hasQueryParams}}{{#hasHeaderParams}} + {{/hasVars}}{{/isExplode}}{{^isExplode}}localVarQueryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/isExplode}}{{/queryParams}}{{/hasQueryParams}}{{#hasHeaderParams}} {{#headerParams}}if ({{paramName}} != null) localVarHeaderParams.add("{{baseName}}", apiClient.parameterToString({{paramName}}));{{^-last}} @@ -164,6 +157,33 @@ public class {{classname}} { {{#returnType}}ParameterizedTypeReference<{{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{.}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{.}}}{{/isResponseFile}}{{/returnType}}> localReturnType = new ParameterizedTypeReference<{{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{.}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{.}}}{{/isResponseFile}}{{/returnType}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localReturnType = new ParameterizedTypeReference() {};{{/returnType}} return apiClient.invokeAPI("{{{path}}}", HttpMethod.{{httpMethod}}, {{#hasPathParams}}uriVariables{{/hasPathParams}}{{^hasPathParams}}Collections.emptyMap(){{/hasPathParams}}, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localReturnType); } + {{#-last}} + + @Override + public ResponseEntity invokeAPI(String url, HttpMethod method, Object request, ParameterizedTypeReference returnType) throws RestClientException { + String localVarPath = url.replace(apiClient.getBasePath(), ""); + Object localVarPostBody = request; + + final Map uriVariables = new HashMap(); + final MultiValueMap localVarQueryParams = new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = new LinkedMultiValueMap(); + + final String[] localVarAccepts = { {{#hasProduces}} + {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} + {{/hasProduces}} }; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = { {{#hasConsumes}} + {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} + {{/hasConsumes}} }; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; + + return apiClient.invokeAPI(localVarPath, method, uriVariables, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, returnType); + } + {{/-last}} {{/operation}} } {{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api_test.mustache index c4551ca7a..e1a213c02 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/api_test.mustache @@ -4,23 +4,27 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.RestClientException; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ -@Ignore -public class {{classname}}Test { +@Disabled +class {{classname}}Test { private final {{classname}} api = new {{classname}}(); @@ -30,15 +34,16 @@ public class {{classname}}Test { * * {{notes}} * - * @throws ApiException + * @throws RestClientException * if the Api call fails */ @Test - public void {{operationId}}Test() { + void {{operationId}}Test() { {{#allParams}} {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.Resource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}} = null; {{/allParams}} - {{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + + {{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{.}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{.}}}{{/isResponseFile}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); // TODO: test validations } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/ApiKeyAuth.mustache index 857403b27..a5662c4a7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/ApiKeyAuth.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/Authentication.mustache index 8e53c2ba0..70b1d1054 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/Authentication.mustache @@ -1,14 +1,19 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and / or query parameters. + * * @param queryParams The query parameters for the request * @param headerParams The header parameters for the request * @param cookieParams The cookie parameters for the request */ - public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams); + void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBasicAuth.mustache index 0194ccf07..4ee900963 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBasicAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.nio.charset.StandardCharsets; @@ -7,6 +9,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBearerAuth.mustache index a0de56641..93fe8732a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/HttpBearerAuth.mustache @@ -1,27 +1,52 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; +import java.util.Optional; +import java.util.function.Supplier; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; - private String bearerToken; + private Supplier tokenSupplier; 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; + return tokenSupplier.get(); } + /** + * 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; + this.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; } @Override public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { + String bearerToken = Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); if (bearerToken == null) { return; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuth.mustache index 7889f1582..55b5da8eb 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuth.mustache @@ -1,24 +1,51 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; +import java.util.Optional; +import java.util.function.Supplier; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; +/** + * Provides support for RFC 6750 - Bearer Token usage for OAUTH 2.0 Authorization. + */ {{>generatedAnnotation}} + public class OAuth implements Authentication { - private String accessToken; + private Supplier tokenSupplier; + /** + * Returns the bearer token used for Authorization. + * + * @return The bearer token + */ public String getAccessToken() { - return accessToken; + return tokenSupplier.get(); } + /** + * Sets the bearer access token used for Authorization. + * + * @param accessToken The bearer token to send in the Authorization header + */ public void setAccessToken(String accessToken) { - this.accessToken = accessToken; + setAccessToken(() -> accessToken); + } + + /** + * Sets the supplier of bearer tokens used for Authorization. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setAccessToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; } @Override public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams) { - if (accessToken != null) { - headerParams.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); - } + Optional.ofNullable(tokenSupplier).map(Supplier::get).ifPresent(accessToken -> + headerParams.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ); } } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuthFlow.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuthFlow.mustache index 7ab35f6d8..759f354f5 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuthFlow.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/auth/OAuthFlow.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; public enum OAuthFlow { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/build.gradle.mustache index 19dfe6d11..7d1725a60 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/build.gradle.mustache @@ -32,8 +32,14 @@ if(hasProperty('target') && target == 'android') { targetSdkVersion 22 } compileOptions { + {{#useJakartaEe}} + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + {{/useJakartaEe}} + {{^useJakartaEe}} sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + {{/useJakartaEe}} } // Rename the aar correctly @@ -57,16 +63,16 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs - classifier = 'sources' + archiveClassifier = 'sources' } artifacts { @@ -78,8 +84,14 @@ if(hasProperty('target') && target == 'android') { apply plugin: 'java' apply plugin: 'maven-publish' + {{#useJakartaEe}} + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + {{/useJakartaEe}} + {{^useJakartaEe}} sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + {{/useJakartaEe}} publishing { publications { @@ -91,33 +103,56 @@ if(hasProperty('target') && target == 'android') { } task execute(type:JavaExec) { - main = System.getProperty('mainClass') + mainClass = System.getProperty('mainClass') classpath = sourceSets.main.runtimeClasspath } } ext { + {{#swagger1AnnotationLibrary}} swagger_annotations_version = "1.6.9" - jackson_version = "2.14.1" - jackson_databind_version = "2.14.1" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} + {{#useJakartaEe}} + spring_web_version = "6.2.8" + jakarta_annotation_version = "2.1.1" + bean_validation_version = "3.0.2" + {{/useJakartaEe}} + {{^useJakartaEe}} + spring_web_version = "5.3.33" jakarta_annotation_version = "1.3.5" - spring_web_version = "5.3.18" + bean_validation_version = "2.0.2" + {{/useJakartaEe}} jodatime_version = "2.9.9" - junit_version = "4.13.2" + junit_version = "5.10.2" } 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 "org.springframework:spring-web:$spring_web_version" implementation "org.springframework:spring-context:$spring_web_version" 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_databind_version" + {{^useJakartaEe}} implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version" + {{/useJakartaEe}} + {{#useJakartaEe}} + implementation "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:$jackson_version" + {{/useJakartaEe}} {{#openApiNullable}} implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" {{/openApiNullable}} @@ -128,7 +163,26 @@ dependencies { {{/joda}} {{#withXml}} implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jackson_version" + implementation "io.github.threeten-jaxb:threeten-jaxb-core:1.2" {{/withXml}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + {{#useBeanValidation}} + implementation "jakarta.validation:jakarta.validation-api:$bean_validation_version" + {{/useBeanValidation}} + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version" +} + +test { + // Enable JUnit 5 (Gradle 4.6+). + useJUnitPlatform() + + // Always run tests, even when nothing changed. + dependsOn 'cleanTest' + + // Show test results. + testLogging { + events "passed", "skipped", "failed" + } + } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/model.mustache new file mode 100644 index 000000000..108748f60 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/model.mustache @@ -0,0 +1,78 @@ +{{>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}} +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/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pojo.mustache new file mode 100644 index 000000000..5ec5d324e --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pojo.mustache @@ -0,0 +1,630 @@ +/** + * {{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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{^isXmlAttribute}} + {{#isDateTime}} + @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) + {{/isDateTime}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{/gson}} + {{^isDiscriminator}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{/isDiscriminator}} + {{#isDiscriminator}} + // The discriminator does not have Nullability-annotation since it is added during serialization by the @JsonTypeName annotation + {{/isDiscriminator}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{.}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} 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}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{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}} + /** + * Constructor with only readonly parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} + */ + {{#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}} +{{#vendorExtensions.x-java-all-args-constructor}} + + /** + * Constructor with all args parameters + */ + public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { +{{#parent}} + super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); +{{/parent}} + {{#vars}} + 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}}; +{{/vars}} + } +{{/vendorExtensions.x-java-all-args-constructor}} + +{{#vars}} + {{^isReadOnly}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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{{nameInPascalCase}}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{{nameInPascalCase}}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}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#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}}({{>nullable_var_annotations}} {{{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}} +{{>libraries/resttemplate/additional_properties}} + + {{#parent}} + {{#readWriteVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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}} + {{/readWriteVars}} + {{/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}}{{#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({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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{{#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.datatypeWithEnum}}} _item : {{getter}}()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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}} +{{#generateBuilders}} + + {{>javaBuilder}}{{! prevent indent}} +{{/generateBuilders}} + +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pom.mustache index 5803c39a4..658e7d486 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/pom.mustache @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M1 + 3.4.0 enforce-maven @@ -63,18 +63,27 @@ org.apache.maven.plugins maven-surefire-plugin - 2.12 + 3.1.2 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods - pertest + false + true + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-version} + + maven-dependency-plugin @@ -95,11 +104,10 @@ org.apache.maven.plugins maven-jar-plugin - 2.2 + 3.3.0 - jar test-jar @@ -111,7 +119,7 @@ org.codehaus.mojo build-helper-maven-plugin - 1.10 + 3.4.0 add_sources @@ -142,22 +150,22 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.11.0 - {{#useJakartaEe}} + {{#useJakartaEe}} 17 17 - {{/useJakartaEe}} - {{^useJakartaEe}} + {{/useJakartaEe}} + {{^useJakartaEe}} 1.8 1.8 - {{/useJakartaEe}} + {{/useJakartaEe}} org.apache.maven.plugins maven-javadoc-plugin - 3.3.2 + 3.5.0 none @@ -173,7 +181,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.3.0 attach-sources @@ -218,6 +226,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -254,11 +269,20 @@ jackson-databind ${jackson-databind-version} + {{^useJakartaEe}} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider ${jackson-version} + {{/useJakartaEe}} + {{#useJakartaEe}} + + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-json-provider + ${jackson-version} + + {{/useJakartaEe}} {{#openApiNullable}} org.openapitools @@ -297,6 +321,23 @@ ${jodatime-version} {{/joda}} + {{#useBeanValidation}} + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + + {{/useBeanValidation}} + {{#performBeanValidation}} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator-version} + + {{/performBeanValidation}} jakarta.annotation jakarta.annotation-api @@ -306,36 +347,41 @@ - junit - junit + org.junit.jupiter + junit-jupiter-engine ${junit-version} test UTF-8 + {{#swagger1AnnotationLibrary}} 1.6.9 - {{#useJakartaEe}} - 6.0.3 - {{/useJakartaEe}} - {{^useJakartaEe}} - 5.3.24 - {{/useJakartaEe}} - 2.14.1 - 2.14.1 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 2.19.2 + 2.19.2 {{#openApiNullable}} - 0.2.4 + 0.2.9 {{/openApiNullable}} - {{#useJakartaEe}} + {{#useJakartaEe}} + 6.2.8 2.1.1 - {{/useJakartaEe}} - {{^useJakartaEe}} + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} + 5.3.33 1.3.5 - {{/useJakartaEe}} - {{#joda}} + 2.0.2 + {{/useJakartaEe}} + {{#joda}} 2.9.9 {{/joda}} - 1.0.0 - 4.13.2 + {{#performBeanValidation}} + 8.0.3.Final + {{/performBeanValidation}} + 5.10.2 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/ApiClient.mustache index 8e40fd194..2ab157d28 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/ApiClient.mustache @@ -1,8 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}; +{{#gson}} import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.google.gson.JsonElement; +{{/gson}} import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.RequestBody; @@ -20,9 +24,14 @@ import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; {{/useRxJava2}} {{#useRxJava3}} -import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory; +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory; {{/useRxJava3}} +{{#gson}} import retrofit2.converter.gson.GsonConverterFactory; +{{/gson}} +{{#jackson}} +import retrofit2.converter.jackson.JacksonConverterFactory; +{{/jackson}} import retrofit2.converter.scalars.ScalarsConverterFactory; import {{invokerPackage}}.auth.HttpBasicAuth; import {{invokerPackage}}.auth.HttpBearerAuth; @@ -37,18 +46,20 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.text.DateFormat; +{{#jsr310}} import java.time.format.DateTimeFormatter; +{{/jsr310}} import java.util.LinkedHashMap; import java.util.Map; import java.util.HashMap; public class ApiClient { - private Map apiAuthorizations; - private OkHttpClient.Builder okBuilder; - private Retrofit.Builder adapterBuilder; - private JSON json; - private OkHttpClient okHttpClient; + protected Map apiAuthorizations; + protected OkHttpClient.Builder okBuilder; + protected Retrofit.Builder adapterBuilder; + protected JSON json; + protected OkHttpClient okHttpClient; public ApiClient() { apiAuthorizations = new LinkedHashMap(); @@ -66,13 +77,13 @@ public class ApiClient { this(); for(String authName : authNames) { {{#hasAuthMethods}} - Interceptor auth; + Interceptor auth = null; {{#authMethods}}if ("{{name}}".equals(authName)) { - {{#isBasic}}{{#isBasicBasic}} + {{#isBasicBasic}} auth = new HttpBasicAuth(); - {{/isBasicBasic}}{{^isBasicBasic}} + {{/isBasicBasic}}{{#isBasicBearer}} auth = new HttpBearerAuth("{{scheme}}"); - {{/isBasicBasic}}{{/isBasic}} + {{/isBasicBearer}} {{#isApiKey}} auth = new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"); {{/isApiKey}} @@ -82,8 +93,9 @@ public class ApiClient { } else {{/authMethods}}{ throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); } - - addAuthorization(authName, auth); + if (auth != null) { + addAuthorization(authName, auth); + } {{/hasAuthMethods}} {{^hasAuthMethods}} throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); @@ -156,7 +168,12 @@ public class ApiClient { .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) {{/useRxJava3}} .addConverterFactory(ScalarsConverterFactory.create()) + {{#jackson}} + .addConverterFactory(JacksonConverterFactory.create(json.getMapper())); + {{/jackson}} + {{#gson}} .addConverterFactory(GsonCustomConverterFactory.create(json.getGson())); + {{/gson}} } public S createService(Class serviceClass) { @@ -172,6 +189,7 @@ public class ApiClient { return this; } + {{#gson}} public ApiClient setSqlDateFormat(DateFormat dateFormat) { this.json.setSqlDateFormat(dateFormat); return this; @@ -199,8 +217,9 @@ public class ApiClient { this.json.setLocalDateFormat(dateFormat); return this; } - {{/jsr310}} + {{/gson}} + /** * Helper method to configure the first api key found @@ -400,14 +419,15 @@ public class ApiClient { } } +{{#gson}} /** * This wrapper is to take care of this case: * when the deserialization fails due to JsonParseException and the * expected type is String, then just return the body string. */ class GsonResponseBodyConverterToString implements Converter { - private final Gson gson; - private final Type type; + protected final Gson gson; + protected final Type type; GsonResponseBodyConverterToString(Gson gson, Type type) { this.gson = gson; @@ -427,14 +447,14 @@ class GsonResponseBodyConverterToString implements Converter class GsonCustomConverterFactory extends Converter.Factory { - private final Gson gson; - private final GsonConverterFactory gsonConverterFactory; + protected final Gson gson; + protected final GsonConverterFactory gsonConverterFactory; public static GsonCustomConverterFactory create(Gson gson) { return new GsonCustomConverterFactory(gson); } - private GsonCustomConverterFactory(Gson gson) { + protected GsonCustomConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; @@ -454,3 +474,4 @@ class GsonCustomConverterFactory extends Converter.Factory return gsonConverterFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); } } +{{/gson}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/CollectionFormats.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/CollectionFormats.mustache index dbfa4ae76..d63c8a864 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/CollectionFormats.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/CollectionFormats.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.Arrays; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON.mustache index 2ff8b1cb7..2e986485f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON.mustache @@ -30,9 +30,11 @@ import java.lang.reflect.Type; import java.text.DateFormat; import java.text.ParseException; import java.text.ParsePosition; +{{#jsr310}} import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +{{/jsr310}} import java.util.Date; import java.util.Locale; import java.util.Map; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON_jackson.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON_jackson.mustache new file mode 100644 index 000000000..c49cbd0f4 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/JSON_jackson.mustache @@ -0,0 +1,264 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +{{#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; +import {{javaxPackage}}.ws.rs.core.GenericType; +import {{javaxPackage}}.ws.rs.ext.ContextResolver; + +{{>generatedAnnotation}} + +public class JSON implements ContextResolver { + private ObjectMapper mapper; + + public JSON() { + mapper = JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .defaultDateFormat(new RFC3339DateFormat()) + .addModule(new JavaTimeModule()) + {{#joda}} + .addModule(new JodaModule()) + {{/joda}} + {{#openApiNullable}} + .addModule(new JsonNullableModule()) + {{/openApiNullable}} + .build(); + } + + /** + * Set the date format for JSON (de)serialization with Date properties. + * @param dateFormat Date format + */ + public void setDateFormat(DateFormat dateFormat) { + mapper.setDateFormat(dateFormat); + } + + @Override + public ObjectMapper getContext(Class type) { + return mapper; + } + + /** + * 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. + */ + 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. + */ + 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. + */ + 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 (GenericType childType : descendants.values()) { + if (isInstanceOf(childType.getRawType(), 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/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api.mustache index 75418b07c..0b0105515 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api.mustache @@ -28,14 +28,17 @@ import okhttp3.MultipartBody; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} {{#operations}} public interface {{classname}} { {{#operation}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api_test.mustache index 8b547d9e9..b84e6b173 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/api_test.mustache @@ -3,18 +3,22 @@ package {{package}}; import {{invokerPackage}}.ApiClient; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ @@ -22,7 +26,7 @@ public class {{classname}}Test { private {{classname}} api; - @Before + @BeforeEach public void setup() { api = new ApiClient().createService({{classname}}.class); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/ApiKeyAuth.mustache index bcc62b284..a3b9b567a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/ApiKeyAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.io.IOException; @@ -64,7 +66,7 @@ public class ApiKeyAuth implements Interceptor { .build(); } else if ("cookie".equals(location)) { request = request.newBuilder() - .addHeader("Cookie", String.format("%s=%s", paramName, apiKey)) + .addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", paramName, apiKey)) .build(); } return chain.proceed(request); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBasicAuth.mustache index 9bd756eb8..ac0347648 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBasicAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.io.IOException; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBearerAuth.mustache index da1bef57a..764c8f216 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/HttpBearerAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.io.IOException; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuth.mustache index 212763dd9..6ea77b78d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuthOkHttpClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuthOkHttpClient.mustache index a499c7b8b..5fe013184 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuthOkHttpClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/auth/OAuthOkHttpClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.io.IOException; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.gradle.mustache index 5deba46d4..30ca11bca 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.gradle.mustache @@ -57,9 +57,9 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } @@ -98,18 +98,21 @@ if(hasProperty('target') && target == 'android') { ext { oltu_version = "1.0.1" - retrofit_version = "2.3.0" + retrofit_version = "2.11.0" + {{#jackson}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" + javax_ws_rs_api_version = "2.1.1" + {{#openApiNullable}} + jackson_databind_nullable_version = "0.2.9" + {{/openApiNullable}} + {{/jackson}} {{#usePlayWS}} - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" - {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" - {{/openApiNullable}} play_version = "2.6.7" {{/usePlayWS}} jakarta_annotation_version = "1.3.5" swagger_annotations_version = "1.5.22" - junit_version = "4.13.2" + junit_version = "5.10.3" {{#useRxJava2}} rx_java_version = "2.1.1" {{/useRxJava2}} @@ -119,7 +122,7 @@ ext { {{#joda}} jodatime_version = "2.9.9" {{/joda}} - json_fire_version = "1.8.0" + json_fire_version = "1.9.0" } dependencies { @@ -131,7 +134,7 @@ dependencies { implementation "io.reactivex.rxjava2:rxjava:$rx_java_version" {{/useRxJava2}} {{#useRxJava3}} - implementation 'com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0' + implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version" implementation "io.reactivex.rxjava3:rxjava:$rx_java_version" {{/useRxJava3}} implementation "io.swagger:swagger-annotations:$swagger_annotations_version" @@ -145,16 +148,19 @@ dependencies { {{/joda}} {{#usePlayWS}} implementation "com.typesafe.play:play-ahc-ws_2.12:$play_version" - implementation "jakarta.validation:jakarta.validation-api:2.0.2" + {{/usePlayWS}} + {{#jackson}} + implementation "jakarta.validation:jakarta.validation-api:3.0.2" implementation "com.squareup.retrofit2:converter-jackson:$retrofit_version" 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_databind_version" + implementation "javax.ws.rs:javax.ws.rs-api:$javax_ws_rs_api_version" {{#openApiNullable}} implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" {{/openApiNullable}} implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" - {{/usePlayWS}} + {{/jackson}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.sbt.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.sbt.mustache index 0bc66fde8..c7113662a 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.sbt.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/build.sbt.mustache @@ -3,31 +3,31 @@ lazy val root = (project in file(".")). organization := "{{groupId}}", name := "{{artifactId}}", version := "{{artifactVersion}}", - scalaVersion := "2.11.4", + scalaVersion := "2.11.12", scalacOptions ++= Seq("-feature"), - javacOptions in compile ++= Seq("-Xlint:deprecation"), - publishArtifact in (Compile, packageDoc) := false, + compile / javacOptions ++= Seq("-Xlint:deprecation"), + Compile / packageDoc / publishArtifact := false, resolvers += Resolver.mavenLocal, libraryDependencies ++= Seq( - "com.squareup.retrofit2" % "retrofit" % "2.3.0" % "compile", - "com.squareup.retrofit2" % "converter-scalars" % "2.3.0" % "compile", + "com.squareup.retrofit2" % "retrofit" % "2.11.0" % "compile", + "com.squareup.retrofit2" % "converter-scalars" % "2.11.0" % "compile", {{^usePlayWS}} - "com.squareup.retrofit2" % "converter-gson" % "2.3.0" % "compile", + "com.squareup.retrofit2" % "converter-gson" % "2.11.0" % "compile", {{/usePlayWS}} {{#usePlayWS}} "com.typesafe.play" % "play-ahc-ws_2.12" % "2.6.7" % "compile", - "jakarta.validation" % "jakarta.validation-api" % "2.0.2" % "compile", - "com.squareup.retrofit2" % "converter-jackson" % "2.3.0" % "compile", - "com.fasterxml.jackson.core" % "jackson-core" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-annotations" % "2.13.4" % "compile", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.4.2" % "compile", + "jakarta.validation" % "jakarta.validation-api" % "3.0.2" % "compile", + "com.squareup.retrofit2" % "converter-jackson" % "2.11.0" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.17.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1" % "compile", {{/usePlayWS}} {{#useRxJava2}} - "com.squareup.retrofit2" % "adapter-rxjava2" % "2.3.0" % "compile", + "com.squareup.retrofit2" % "adapter-rxjava2" % "2.11.0" % "compile", "io.reactivex.rxjava2" % "rxjava" % "2.1.1" % "compile", {{/useRxJava2}} {{#useRxJava3}} - "com.github.akarnokd" % "rxjava3-retrofit-adapter" % "3.0.0" % "compile", + "com.squareup.retrofit2" % "adapter-rxjava3" % "2.11.0" % "compile", "io.reactivex.rxjava3" % "rxjava" % "3.0.4" % "compile", {{/useRxJava3}} "io.swagger" % "swagger-annotations" % "1.5.21" % "compile", @@ -35,9 +35,9 @@ lazy val root = (project in file(".")). {{#joda}} "joda-time" % "joda-time" % "2.9.9" % "compile", {{/joda}} - "io.gsonfire" % "gson-fire" % "1.8.0" % "compile", + "io.gsonfire" % "gson-fire" % "1.9.0" % "compile", "jakarta.annotation" % "jakarta.annotation-api" % "1.3.5" % "compile", - "junit" % "junit" % "4.13.2" % "test", + "org.junit.jupiter" % "junit-jupiter-api" % "5.10.3" % "test", "com.novocode" % "junit-interface" % "0.11" % "test" ) ) diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache index 220d89653..35d4f6d33 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache @@ -11,6 +11,7 @@ import java.util.List; * Holds ApiKey auth info */ {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/ApiClient.mustache index c43f800a4..9d64c3343 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.io.IOException; @@ -29,13 +31,13 @@ import {{invokerPackage}}.auth.Authentication; public class ApiClient { /** Underlying HTTP-client */ - private WSClient wsClient; + protected WSClient wsClient; /** Supported auths */ - private Map authentications; + protected Map authentications; /** API base path */ - private String basePath = "{{{basePath}}}"; + protected String basePath = "{{{basePath}}}"; public ApiClient(WSClient wsClient) { this(); @@ -45,8 +47,8 @@ public class ApiClient { public ApiClient() { // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap<>();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - // authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + // authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} // authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache index d9ed3330f..d6411f26f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import play.libs.F; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallFactory.mustache index b17ac6415..ee9a4c68f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/Play24CallFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import okhttp3.*; @@ -60,14 +62,14 @@ public class Play24CallFactory implements okhttp3.Call.Factory { rb.addHeader(header.getKey(), header.getValue()); } for (Map.Entry cookie : this.extraCookies.entrySet()) { - rb.addHeader("Cookie", String.format("%s=%s", cookie.getKey(), cookie.getValue())); + rb.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getKey(), cookie.getValue())); } // add extra query params if (!this.extraQueryParams.isEmpty()) { String newQuery = request.url().uri().getQuery(); for (Pair queryParam : this.extraQueryParams) { - String param = String.format("%s=%s", queryParam.getName(), queryParam.getValue()); + String param = String.format(java.util.Locale.ROOT, "%s=%s", queryParam.getName(), queryParam.getValue()); if (newQuery == null) { newQuery = param; } else { @@ -174,7 +176,7 @@ public class Play24CallFactory implements okhttp3.Call.Factory { String delimiter = ""; final StringBuilder cookieHeader = new StringBuilder(); for (final String cookie : cookies) { - cookieHeader.append(String.format("%s%s", delimiter, cookie)); + cookieHeader.append(String.format(java.util.Locale.ROOT, "%s%s", delimiter, cookie)); delimiter = "; "; } wsRequest.setHeader("Cookie", cookieHeader.toString()); @@ -219,7 +221,7 @@ public class Play24CallFactory implements okhttp3.Call.Factory { } } for (final WSCookie cookie : r.getCookies()) { - builder.addHeader("Cookie", String.format("%s=%s", cookie.getName(), cookie.getValue())); + builder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getName(), cookie.getValue())); } builder.protocol(Protocol.HTTP_1_1); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/api.mustache index 2b7512d19..f793699be 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play24/api.mustache @@ -14,12 +14,10 @@ import okhttp3.MultipartBody; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} import play.libs.F; import retrofit2.Response; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/ApiClient.mustache index 849e0665e..3125404bc 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.io.IOException; @@ -29,13 +31,13 @@ import {{invokerPackage}}.auth.Authentication; public class ApiClient { /** Underlying HTTP-client */ - private WSClient wsClient; + protected WSClient wsClient; /** Supported auths */ - private Map authentications; + protected Map authentications; /** API base path */ - private String basePath = "{{{basePath}}}"; + protected String basePath = "{{{basePath}}}"; public ApiClient(WSClient wsClient) { this(); @@ -44,8 +46,8 @@ public class ApiClient { public ApiClient() { // Setup authentications (key: authentication name, value: authentication). - authentications = new HashMap<>();{{#authMethods}}{{#isBasic}} - // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}} + authentications = new HashMap<>();{{#authMethods}}{{#isBasicBasic}} + // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"query"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} // authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache index 62360bf98..b431690f9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.concurrent.CompletionStage; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallFactory.mustache index a9f19474f..0ac708b2c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/Play25CallFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import okhttp3.*; @@ -69,14 +71,14 @@ public class Play25CallFactory implements okhttp3.Call.Factory { rb.addHeader(header.getKey(), header.getValue()); } for (Map.Entry cookie : this.extraCookies.entrySet()) { - rb.addHeader("Cookie", String.format("%s=%s", cookie.getKey(), cookie.getValue())); + rb.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getKey(), cookie.getValue())); } // add extra query params if (!this.extraQueryParams.isEmpty()) { String newQuery = request.url().uri().getQuery(); for (Pair queryParam : this.extraQueryParams) { - String param = String.format("%s=%s", queryParam.getName(), queryParam.getValue()); + String param = String.format(java.util.Locale.ROOT, "%s=%s", queryParam.getName(), queryParam.getValue()); if (newQuery == null) { newQuery = param; } else { @@ -179,7 +181,7 @@ public class Play25CallFactory implements okhttp3.Call.Factory { String delimiter = ""; final StringBuilder cookieHeader = new StringBuilder(); for (final String cookie : cookies) { - cookieHeader.append(String.format("%s%s", delimiter, cookie)); + cookieHeader.append(String.format(java.util.Locale.ROOT, "%s%s", delimiter, cookie)); delimiter = "; "; } wsRequest.setHeader("Cookie", cookieHeader.toString()); @@ -228,7 +230,7 @@ public class Play25CallFactory implements okhttp3.Call.Factory { } } for (final WSCookie cookie : r.getCookies()) { - builder.addHeader("Cookie", String.format("%s=%s", cookie.getName(), cookie.getValue())); + builder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getName(), cookie.getValue())); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/api.mustache index d4e97cc34..de6a6f6d9 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play25/api.mustache @@ -14,12 +14,10 @@ import okhttp3.MultipartBody; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} import java.util.concurrent.*; import retrofit2.Response; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/ApiClient.mustache index b752b433d..4365b2aab 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.io.File; @@ -35,22 +37,22 @@ import {{invokerPackage}}.auth.Authentication; public class ApiClient { /** Underlying HTTP-client */ - private WSClient wsClient; + protected WSClient wsClient; /** Creates HTTP call instances */ - private Play26CallFactory callFactory; + protected Play26CallFactory callFactory; /** Create {@link java.util.concurrent.CompletionStage} instances from HTTP calls */ - private Play26CallAdapterFactory callAdapterFactory; + protected Play26CallAdapterFactory callAdapterFactory; /** Supported auths */ - private Map authentications; + protected Map authentications; /** API base path */ - private String basePath = "{{{basePath}}}"; + protected String basePath = "{{{basePath}}}"; /** Default ObjectMapper */ - private ObjectMapper defaultMapper; + protected ObjectMapper defaultMapper; public ApiClient(WSClient wsClient) { this(); @@ -59,8 +61,8 @@ public class ApiClient { public ApiClient() { // Setup authentications (key: authentication name, value: authentication). - authentications = new HashMap<>();{{#authMethods}}{{#isBasic}} - // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}} + authentications = new HashMap<>();{{#authMethods}}{{#isBasicBasic}} + // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} // authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallAdapterFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallAdapterFactory.mustache index 05c725474..2b7739fe8 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallAdapterFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallAdapterFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import java.util.concurrent.CompletionStage; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallFactory.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallFactory.mustache index f65c921a7..719fec2d8 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallFactory.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/Play26CallFactory.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import okhttp3.*; @@ -78,14 +80,14 @@ public class Play26CallFactory implements okhttp3.Call.Factory { rb.addHeader(header.getKey(), header.getValue()); } for (Map.Entry cookie : this.extraCookies.entrySet()) { - rb.addHeader("Cookie", String.format("%s=%s", cookie.getKey(), cookie.getValue())); + rb.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getKey(), cookie.getValue())); } // add extra query params if (!this.extraQueryParams.isEmpty()) { String newQuery = request.url().uri().getQuery(); for (Pair queryParam : this.extraQueryParams) { - String param = String.format("%s=%s", queryParam.getName(), queryParam.getValue()); + String param = String.format(java.util.Locale.ROOT, "%s=%s", queryParam.getName(), queryParam.getValue()); if (newQuery == null) { newQuery = param; } else { @@ -258,7 +260,7 @@ public class Play26CallFactory implements okhttp3.Call.Factory { } } for (final WSCookie cookie : r.getCookies()) { - builder.addHeader("Cookie", String.format("%s=%s", cookie.getName(), cookie.getValue())); + builder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", cookie.getName(), cookie.getValue())); } builder.message(r.getStatusText()); diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/api.mustache index dd3339ff8..7f7b9e2b0 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/play26/api.mustache @@ -17,12 +17,15 @@ import okhttp3.MultipartBody; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} import java.util.concurrent.*; import retrofit2.Response; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/pom.mustache index 5ec0dd025..f0deaafef 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/retrofit2/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 2.12 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods pertest @@ -217,11 +217,13 @@ jsr305 3.0.2 + {{#gson}} com.squareup.retrofit2 converter-gson ${retrofit-version} + {{/gson}} com.squareup.retrofit2 retrofit @@ -243,11 +245,13 @@ + {{#gson}} io.gsonfire gson-fire ${gson-fire-version} + {{/gson}} {{#joda}} joda-time @@ -274,12 +278,12 @@ ${rxjava-version} - com.github.akarnokd - rxjava3-retrofit-adapter - 3.0.0 + com.squareup.retrofit2 + adapter-rxjava3 + ${retrofit-version} {{/useRxJava3}} - {{#usePlayWS}} + {{#jackson}} com.squareup.retrofit2 @@ -321,6 +325,20 @@ ${jackson-version} {{/withXml}} + {{#useBeanValidation}} + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + + {{/useBeanValidation}} + + javax.ws.rs + javax.ws.rs-api + ${javax.ws.rs-api-version} + + {{/jackson}} + {{#usePlayWS}} com.typesafe.play play-ahc-ws_2.12 @@ -349,8 +367,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test @@ -360,17 +378,22 @@ 1.8 ${java.version} ${java.version} - 1.8.3 + {{#gson}} + 1.9.0 + {{/gson}} 1.6.3 + {{#jackson}} + 2.19.2 + 2.19.2 + {{#openApiNullable}} + 0.2.9 + {{/openApiNullable}} + 2.1.1 + {{/jackson}} {{#usePlayWS}} - 2.13.4 - 2.13.4.2 2.6.7 - {{#openApiNullable}} - 0.2.4 - {{/openApiNullable}} {{/usePlayWS}} - 2.5.0 + 2.11.0 {{#useRxJava2}} 2.1.1 {{/useRxJava2}} @@ -380,11 +403,15 @@ {{#joda}} 2.9.9 {{/joda}} + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 -{{#useBeanValidation}} 2.0.2 -{{/useBeanValidation}} + {{/useJakartaEe}} 1.0.1 - 4.13.2 + 5.10.3 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/ApiClient.mustache index 68253df0b..b0e1103ce 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/ApiClient.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import {{invokerPackage}}.auth.Authentication; @@ -45,23 +47,24 @@ import java.util.regex.Pattern; import static java.util.stream.Collectors.toMap; {{>generatedAnnotation}} + public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { - private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); - private static final OpenOptions FILE_DOWNLOAD_OPTIONS = new OpenOptions().setCreate(true).setTruncateExisting(true); + protected static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + protected static final OpenOptions FILE_DOWNLOAD_OPTIONS = new OpenOptions().setCreate(true).setTruncateExisting(true); - private final Vertx vertx; - private final JsonObject config; - private final String identifier; + protected final Vertx vertx; + protected final JsonObject config; + protected final String identifier; - private MultiMap defaultHeaders = MultiMap.caseInsensitiveMultiMap(); - private MultiMap defaultCookies = MultiMap.caseInsensitiveMultiMap(); - private Map authentications; - private String basePath = "{{{basePath}}}"; - private DateFormat dateFormat; - private ObjectMapper objectMapper; - private String downloadsDir = ""; - private int timeout = -1; + protected MultiMap defaultHeaders = MultiMap.caseInsensitiveMultiMap(); + protected MultiMap defaultCookies = MultiMap.caseInsensitiveMultiMap(); + protected Map authentications; + protected String basePath = "{{{basePath}}}"; + protected DateFormat dateFormat; + protected ObjectMapper objectMapper; + protected String downloadsDir = ""; + protected int timeout = -1; public ApiClient(Vertx vertx, JsonObject config) { Objects.requireNonNull(vertx, "Vertx must not be null"); @@ -79,7 +82,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // Build object mapper this.objectMapper = new ObjectMapper(); this.objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); this.objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); this.objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); @@ -93,8 +96,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { // Setup authentications (key: authentication name, value: authentication). this.authentications = new HashMap<>();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. @@ -383,7 +386,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param mime MIME * @return True if the MIME type is JSON */ - private boolean isJsonMime(String mime) { + protected boolean isJsonMime(String mime) { String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; return mime != null && (mime.matches(jsonMime) || mime.equalsIgnoreCase("application/json-patch+json")); } @@ -521,12 +524,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } - private String buildCookieHeader(MultiMap cookies) { + protected String buildCookieHeader(MultiMap cookies) { final StringBuilder cookieValue = new StringBuilder(); String delimiter = ""; for (final Map.Entry entry : cookies.entries()) { if (entry.getValue() != null) { - cookieValue.append(String.format("%s%s=%s", delimiter, entry.getKey(), entry.getValue())); + cookieValue.append(String.format(java.util.Locale.ROOT, "%s%s=%s", delimiter, entry.getKey(), entry.getValue())); delimiter = "; "; } } @@ -541,7 +544,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return The sanitized filename */ protected String sanitizeFilename(String filename) { - return filename.replaceAll(".*[/\\\\]", ""); + return filename.replaceFirst("^.*[/\\\\]", ""); } /** @@ -678,56 +681,56 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { public static class AuthInfo { - private final Map authentications = new LinkedHashMap<>();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + protected final Map authentications = new LinkedHashMap<>();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - public void add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String username, String password) { + public void add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String username, String password) { HttpBasicAuth auth = new HttpBasicAuth(); auth.setUsername(username); auth.setPassword(password); authentications.put("{{name}}", auth); - }{{/isBasicBasic}}{{^isBasicBasic}} + }{{/isBasicBasic}}{{#isBasicBearer}} - public void add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String bearerToken) { + public void add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String bearerToken) { HttpBearerAuth auth = new HttpBearerAuth("{{scheme}}"); auth.setBearerToken(bearerToken); authentications.put("{{name}}", auth); - }{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + }{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} - public void add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String apikey, String apiKeyPrefix) { + public void add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String apikey, String apiKeyPrefix) { ApiKeyAuth auth = new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}},"{{keyParamName}}"); auth.setApiKey(apikey); auth.setApiKeyPrefix(apiKeyPrefix); authentications.put("{{name}}", auth); }{{/isApiKey}}{{#isOAuth}} - public void add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String accessToken) { + public void add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String accessToken) { OAuth auth = new OAuth(); auth.setAccessToken(accessToken); authentications.put("{{name}}", auth); }{{/isOAuth}}{{/authMethods}}{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - public static AuthInfo for{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}(String username, String password) { + public static AuthInfo for{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}(String username, String password) { AuthInfo authInfo = new AuthInfo(); - authInfo.add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(username, password); + authInfo.add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(username, password); return authInfo; - }{{/isBasicBasic}}{{^isBasicBasic}} + }{{/isBasicBasic}}{{#isBasicBearer}} - public static AuthInfo for{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String bearerToken) { + public static AuthInfo for{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String bearerToken) { AuthInfo authInfo = new AuthInfo(); - authInfo.add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(bearerToken); + authInfo.add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(bearerToken); return authInfo; - }{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + }{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} - public static AuthInfo for{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String apikey, String apiKeyPrefix) { + public static AuthInfo for{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String apikey, String apiKeyPrefix) { AuthInfo authInfo = new AuthInfo(); - authInfo.add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(apikey, apiKeyPrefix); + authInfo.add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(apikey, apiKeyPrefix); return authInfo; }{{/isApiKey}}{{#isOAuth}} - public static AuthInfo for{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(String accessToken) { + public static AuthInfo for{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(String accessToken) { AuthInfo authInfo = new AuthInfo(); - authInfo.add{{#lambda.titlecase}}{{name}}{{/lambda.titlecase}}Authentication(accessToken); + authInfo.add{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(accessToken); return authInfo; }{{/isOAuth}}{{/authMethods}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/Configuration.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/Configuration.mustache index 17710ae16..5f26ea84b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/Configuration.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/Configuration.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}; import io.vertx.core.Vertx; @@ -6,6 +8,7 @@ import io.vertx.core.json.JsonObject; import java.util.Objects; public class Configuration { + public static final String VERSION = "{{{artifactVersion}}}"; private static ApiClient defaultApiClient = null; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api.mustache index b81c06100..6a069a773 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api.mustache @@ -5,6 +5,10 @@ import {{invokerPackage}}.ApiClient; {{/imports}} import io.vertx.core.AsyncResult; import io.vertx.core.Handler; +{{#supportVertxFuture}} +import io.vertx.core.Future; +import io.vertx.core.Promise; +{{/supportVertxFuture}} import io.vertx.core.json.JsonObject; import java.util.*; @@ -16,13 +20,35 @@ public interface {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - void {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}, {{/allParams}}Handler> handler); + void {{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}Handler> handler); + {{#supportVertxFuture}} {{#isDeprecated}} @Deprecated {{/isDeprecated}} - void {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}, {{/allParams}}ApiClient.AuthInfo authInfo, Handler> handler); + default Future<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){ + Promise<{{{returnType}}}{{^returnType}}Void{{/returnType}}> promise = Promise.promise(); + {{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}promise); + return promise.future(); + } + {{/supportVertxFuture}} + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + void {{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}ApiClient.AuthInfo authInfo, Handler> handler); + + {{#supportVertxFuture}} + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + default Future<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{operationId}}({{#allParams}}{{>nullable_var_annotations}} {{{dataType}}} {{paramName}}, {{/allParams}}ApiClient.AuthInfo authInfo){ + Promise<{{{returnType}}}{{^returnType}}Void{{/returnType}}> promise = Promise.promise(); + {{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}authInfo, promise); + return promise.future(); + } + + {{/supportVertxFuture}} {{/operation}} {{/operations}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiException.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiException.mustache index 6e9bbdbb8..0445d1f08 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiException.mustache @@ -7,7 +7,10 @@ import io.vertx.core.Future; import io.vertx.core.MultiMap; {{>generatedAnnotation}} + public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private static final long serialVersionUID = 1L; + private int code = 0; private MultiMap responseHeaders = null; private String responseBody = null; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiImpl.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiImpl.mustache index 35daaade9..1b6f9ba10 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiImpl.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/apiImpl.mustache @@ -21,6 +21,7 @@ import {{invokerPackage}}.Configuration; import {{invokerPackage}}.Pair; {{>generatedAnnotation}} + {{#operations}} public class {{classname}}Impl implements {{classname}} { @@ -112,7 +113,7 @@ public class {{classname}}Impl implements {{classname}} { private String encodeParameter(String parameter) { try { - return URLEncoder.encode(parameter, StandardCharsets.UTF_8.name()); + return URLEncoder.encode(parameter, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); } catch (UnsupportedEncodingException e) { return parameter; } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api_test.mustache index f4fee565f..1469d11fb 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/api_test.mustache @@ -6,11 +6,10 @@ package {{package}}; import {{invokerPackage}}.Configuration; -import org.junit.Test; -import org.junit.Ignore; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; @@ -23,34 +22,26 @@ import io.vertx.ext.unit.Async; import java.time.LocalDate; import java.time.OffsetDateTime; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -{{/fullJavaUtil}} /** * API tests for {{classname}} */ -@RunWith(VertxUnitRunner.class) -@Ignore +@Disabled public class {{classname}}Test { private {{classname}} api; - @Rule - public RunTestOnContext rule = new RunTestOnContext(); - - @BeforeClass + @BeforeAll public void setupApiClient() { - JsonObject config = new JsonObject(); - Vertx vertx = rule.vertx(); - Configuration.setupDefaultApiClient(vertx, config); - api = new {{classname}}Impl(); } - {{#operations}}{{#operation}} + + {{#operations}} + {{#operation}} /** * {{summary}} * {{notes}} @@ -68,5 +59,6 @@ public class {{classname}}Test { async.complete(); }); } - {{/operation}}{{/operations}} + {{/operation}} + {{/operations}} } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/ApiKeyAuth.mustache index fadbd33e5..f89a627e5 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/ApiKeyAuth.mustache @@ -8,6 +8,7 @@ import io.vertx.core.MultiMap; import java.util.List; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/Authentication.mustache index 9d546c300..824a87500 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/Authentication.mustache @@ -7,6 +7,8 @@ import io.vertx.core.MultiMap; import java.util.List; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and query params. diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBasicAuth.mustache index 9bca5090a..6f606b98b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBasicAuth.mustache @@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBearerAuth.mustache index 208eedb71..52b284faf 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/HttpBearerAuth.mustache @@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; private String bearerToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/OAuth.mustache index 5bebabf90..caa215717 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/auth/OAuth.mustache @@ -8,6 +8,7 @@ import io.vertx.core.MultiMap; import java.util.List; {{>generatedAnnotation}} + public class OAuth implements Authentication { private String accessToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/build.gradle.mustache index 6f9d9e6bb..2ac2ac52d 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/build.gradle.mustache @@ -30,12 +30,12 @@ task execute(type:JavaExec) { ext { swagger_annotations_version = "1.5.21" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" - vertx_version = "3.4.2" - junit_version = "4.13.2" + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" + vertx_version = "{{#supportVertxFuture}}4.0.0{{/supportVertxFuture}}{{^supportVertxFuture}}3.5.2{{/supportVertxFuture}}" + junit_version = "5.10.3" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} jakarta_annotation_version = "1.3.5" } @@ -56,6 +56,6 @@ dependencies { implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" {{/openApiNullable}} implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" testImplementation "io.vertx:vertx-unit:$vertx_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/pom.mustache index 1a10de313..af0c9507b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/pom.mustache @@ -65,12 +65,12 @@ maven-surefire-plugin 2.12 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods pertest @@ -213,6 +213,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -277,8 +284,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test @@ -292,12 +299,22 @@ UTF-8 - 3.4.2 + {{#supportVertxFuture}}4.0.0{{/supportVertxFuture}}{{^supportVertxFuture}}3.5.2{{/supportVertxFuture}} + {{#swagger1AnnotationLibrary}} 1.6.6 - 2.13.4 - 2.13.4.2 - 0.2.4 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 2.19.2 + 2.19.2 + 0.2.9 + {{#useJakartaEe}} + 2.1.1 + {{/useJakartaEe}} + {{^useJakartaEe}} 1.3.5 - 4.13.2 + {{/useJakartaEe}} + 5.10.3 diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/rxApiImpl.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/rxApiImpl.mustache index f01030420..4d655b47c 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/vertx/rxApiImpl.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/vertx/rxApiImpl.mustache @@ -11,6 +11,7 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Handler; {{>generatedAnnotation}} + {{#operations}} public class {{classname}} { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/ApiClient.mustache index 16fa4dd71..cd14c31d2 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/ApiClient.mustache @@ -1,13 +1,17 @@ +{{>licenseInfo}} + package {{invokerPackage}}; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; {{#openApiNullable}} import org.openapitools.jackson.nullable.JsonNullableModule; {{/openApiNullable}} -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +{{#generateClientAsBean}} +import org.springframework.beans.factory.annotation.Autowired; +{{/generateClientAsBean}} import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -22,13 +26,16 @@ import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; +{{#generateClientAsBean}} +import org.springframework.stereotype.Component; +{{/generateClientAsBean}} import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.web.client.RestClientException; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.reactive.function.client.WebClient; @@ -45,7 +52,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.DateFormat; @@ -62,8 +68,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; -{{#useJakartaEe}}import jakarta.annotation.Nullable;{{/useJakartaEe}} -{{^useJakartaEe}}import javax.annotation.Nullable;{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Nullable; {{#jsr310}} import java.time.OffsetDateTime; @@ -78,32 +83,36 @@ import {{invokerPackage}}.auth.OAuth; {{/hasOAuthMethods}} {{>generatedAnnotation}} + +{{#generateClientAsBean}} +@Component("{{invokerPackage}}.ApiClient") +{{/generateClientAsBean}} public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { public enum CollectionFormat { CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null); - private final String separator; - private CollectionFormat(String separator) { + protected final String separator; + CollectionFormat(String separator) { this.separator = separator; } - private String collectionToString(Collection collection) { + protected String collectionToString(Collection collection) { return StringUtils.collectionToDelimitedString(collection, separator); } } - private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; + protected static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; - private HttpHeaders defaultHeaders = new HttpHeaders(); - private MultiValueMap defaultCookies = new LinkedMultiValueMap(); + protected HttpHeaders defaultHeaders = new HttpHeaders(); + protected MultiValueMap defaultCookies = new LinkedMultiValueMap(); - private String basePath = "{{basePath}}"; + protected String basePath = "{{basePath}}"; - private final WebClient webClient; - private final DateFormat dateFormat; - private final ObjectMapper objectMapper; + protected final WebClient webClient; + protected final DateFormat dateFormat; + protected final ObjectMapper objectMapper; - private Map authentications; + protected Map authentications; public ApiClient() { @@ -113,6 +122,9 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { this.init(); } + {{#generateClientAsBean}} + @Autowired + {{/generateClientAsBean}} public ApiClient(WebClient webClient) { this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient()), createDefaultDateFormat()); } @@ -125,7 +137,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient(mapper.copy())), format); } - private ApiClient(WebClient webClient, DateFormat format) { + protected ApiClient(WebClient webClient, DateFormat format) { this.webClient = webClient; this.dateFormat = format; this.objectMapper = createDefaultObjectMapper(format); @@ -145,7 +157,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(dateFormat); mapper.registerModule(new JavaTimeModule()); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}); {{#openApiNullable}} JsonNullableModule jnm = new JsonNullableModule(); mapper.registerModule(jnm); @@ -156,8 +168,8 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { protected void init() { // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} // Prevent the authentications from being modified. @@ -345,10 +357,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @return ApiClient this client */ public ApiClient addDefaultHeader(String name, String value) { - if (defaultHeaders.containsKey(name)) { - defaultHeaders.remove(name); - } - defaultHeaders.add(name, value); + defaultHeaders.set(name, value); return this; } @@ -435,6 +444,36 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { } } + /** + * Converts a parameter to a {@link MultiValueMap} containing Json-serialized values for use in REST requests + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the Json-serialized String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMapJson(CollectionFormat collectionFormat, String name, Object value) { + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + try { + return parameterToMultiValueMap(collectionFormat, name, objectMapper.writeValueAsString(value)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + List values = new ArrayList<>(); + for(Object o : valueCollection) { + try { + values.add(objectMapper.writeValueAsString(o)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + return parameterToMultiValueMap(collectionFormat, name, "[" + StringUtils.collectionToDelimitedString(values, collectionFormat.separator) + "]"); + } + /** * Converts a parameter to a {@link MultiValueMap} for use in REST requests * @param collectionFormat The format to convert to @@ -624,7 +663,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param uriParams The path parameters * return templatized query string */ - private String generateQueryUri(MultiValueMap queryParams, Map uriParams) { + protected String generateQueryUri(MultiValueMap queryParams, Map uriParams) { StringBuilder queryBuilder = new StringBuilder(); queryParams.forEach((name, values) -> { if (CollectionUtils.isEmpty(values)) { @@ -650,16 +689,17 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { return queryBuilder.toString(); } - private WebClient.RequestBodySpec prepareRequest(String path, HttpMethod method, Map pathParams, + protected WebClient.RequestBodySpec prepareRequest(String path, HttpMethod method, Map pathParams, MultiValueMap queryParams, Object body, HttpHeaders headerParams, MultiValueMap cookieParams, MultiValueMap formParams, List accept, MediaType contentType, String[] authNames) { updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); - final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path); + final UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(basePath).path(path); String finalUri = builder.build(false).toUriString(); - Map uriParams = new HashMap<>(pathParams); + Map uriParams = new HashMap<>(); + uriParams.putAll(pathParams); if (queryParams != null && !queryParams.isEmpty()) { //Include queryParams in uriParams taking into account the paramName @@ -694,7 +734,12 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { * @param requestBuilder The current request */ protected void addHeadersToRequest(HttpHeaders headers, WebClient.RequestBodySpec requestBuilder) { + {{#useJakartaEe}} + for (Entry> entry : headers.headerSet()) { + {{/useJakartaEe}} + {{^useJakartaEe}} for (Entry> entry : headers.entrySet()) { + {{/useJakartaEe}} List values = entry.getValue(); for(String value : values) { if (value != null) { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/additional_properties.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/additional_properties.mustache new file mode 100644 index 000000000..8e7182792 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/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/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api.mustache index b1320b740..7c8e49209 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api.mustache @@ -4,36 +4,45 @@ import {{invokerPackage}}.ApiClient; {{#imports}}import {{import}}; {{/imports}} -{{^fullJavaUtil}} - -{{#useBeanValidation}} -import jakarta.validation.constraints.*; -import jakarta.validation.Valid; -{{/useBeanValidation}} import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.Arrays; import java.util.stream.Collectors; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} +{{#generateClientAsBean}} import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; -import org.springframework.web.reactive.function.client.WebClientResponseException; +{{/generateClientAsBean}} import org.springframework.core.io.FileSystemResource; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +{{#generateClientAsBean}} +import org.springframework.stereotype.Component; +{{/generateClientAsBean}} +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; +import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; import reactor.core.publisher.Flux; {{>generatedAnnotation}} + +{{#generateClientAsBean}} +@Component("{{package}}.{{classname}}") +{{/generateClientAsBean}} {{#operations}} public class {{classname}} { private ApiClient apiClient; @@ -42,7 +51,9 @@ public class {{classname}} { this(new ApiClient()); } +{{#generateClientAsBean}} @Autowired +{{/generateClientAsBean}} public {{classname}}(ApiClient apiClient) { this.apiClient = apiClient; } @@ -54,8 +65,8 @@ public class {{classname}} { public void setApiClient(ApiClient apiClient) { this.apiClient = apiClient; } - {{#operation}} +{{#singleRequestParameter}}{{>single_request_parameter}}{{/singleRequestParameter}}{{^singleRequestParameter}}{{/singleRequestParameter}} /** * {{summary}} * {{notes}} @@ -74,7 +85,7 @@ public class {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - private ResponseSpec {{operationId}}RequestCreation({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { + private ResponseSpec {{operationId}}RequestCreation({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { Object postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; {{#allParams}} {{#required}} @@ -99,9 +110,26 @@ public class {{classname}} { final MultiValueMap formParams = new LinkedMultiValueMap(); {{#hasQueryParams}} - {{#queryParams}} + {{#queryParams}} + {{#queryIsJsonMimeType}} + queryParams.putAll(apiClient.parameterToMultiValueMapJson({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/queryIsJsonMimeType}} + {{^queryIsJsonMimeType}} + {{#isExplode}} + {{#hasVars}} + {{#vars}} + queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}}.{{getter}}())); + {{/vars}} + {{/hasVars}} + {{^hasVars}} + queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/hasVars}} + {{/isExplode}} + {{^isExplode}} queryParams.putAll(apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{.}}}".toUpperCase(Locale.ROOT)){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); - {{/queryParams}} + {{/isExplode}} + {{/queryIsJsonMimeType}} + {{/queryParams}} {{/hasQueryParams}} {{#hasHeaderParams}} @@ -136,7 +164,7 @@ public class {{classname}} { String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; - {{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} + {{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} return apiClient.invokeAPI("{{{path}}}", HttpMethod.{{httpMethod}}, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType); } @@ -152,9 +180,9 @@ public class {{classname}} { * @see {{summary}} Documentation {{/externalDocs}} */ - public {{#returnType}}{{#vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{returnBaseType}}}>{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#isArray}}Flux<{{{returnBaseType}}}>{{/isArray}}{{^isArray}}Mono<{{{returnType}}}>{{/isArray}}{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{^returnType}}{{#vendorExtensions.x-webclient-blocking}}void{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}Mono{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{operationId}}({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { - {{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} - {{^returnType}}{{^vendorExtensions.x-webclient-blocking}}return {{/vendorExtensions.x-webclient-blocking}}{{/returnType}}{{#returnType}}return {{/returnType}}{{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#isArray}}bodyToFlux{{/isArray}}{{^isArray}}bodyToMono{{/isArray}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}{{/isArray}}.block(){{/vendorExtensions.x-webclient-blocking}}; + public {{#returnType}}{{#vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Flux<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{^returnType}}{{#vendorExtensions.x-webclient-blocking}}void{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}Mono{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{operationId}}({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { + {{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} + {{^returnType}}{{^vendorExtensions.x-webclient-blocking}}return {{/vendorExtensions.x-webclient-blocking}}{{/returnType}}{{#returnType}}return {{/returnType}}{{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#vendorExtensions.x-webclient-return-except-list-of-string}}bodyToFlux{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}bodyToMono{{/vendorExtensions.x-webclient-return-except-list-of-string}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}.block(){{/vendorExtensions.x-webclient-blocking}}; } /** @@ -169,9 +197,9 @@ public class {{classname}} { * @see {{summary}} Documentation {{/externalDocs}} */ - public {{#vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#isArray}}ResponseEntity>{{/isArray}}{{^isArray}}ResponseEntity<{{{returnType}}}>{{/isArray}}{{/returnType}}{{^returnType}}ResponseEntity{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#isArray}}Mono>>{{/isArray}}{{^isArray}}Mono>{{/isArray}}{{/returnType}}{{^returnType}}Mono>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{operationId}}WithHttpInfo({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { - {{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} - return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#isArray}}toEntityList{{/isArray}}{{^isArray}}toEntity{{/isArray}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}.block(){{/vendorExtensions.x-webclient-blocking}}; + public {{#vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}ResponseEntity{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Mono>>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}Mono>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{operationId}}WithHttpInfo({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { + {{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() {};{{/returnType}} + return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#vendorExtensions.x-webclient-return-except-list-of-string}}toEntityList{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}toEntity{{/vendorExtensions.x-webclient-return-except-list-of-string}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}.block(){{/vendorExtensions.x-webclient-blocking}}; } /** @@ -187,7 +215,7 @@ public class {{classname}} { * @see {{summary}} Documentation {{/externalDocs}} */ - public ResponseSpec {{operationId}}WithResponseSpec({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { + public ResponseSpec {{operationId}}WithResponseSpec({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException { return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); } {{/operation}} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api_test.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api_test.mustache index 62a89973b..e0a960d12 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/api_test.mustache @@ -4,21 +4,25 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Test; -import org.junit.Ignore; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -{{^fullJavaUtil}} import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -{{/fullJavaUtil}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; + +{{/useBeanValidation}} /** * API tests for {{classname}} */ -@Ignore +@Disabled public class {{classname}}Test { private final {{classname}} api = new {{classname}}(); @@ -31,10 +35,11 @@ public class {{classname}}Test { */ @Test public void {{operationId}}Test() { + // uncomment below to test the function {{#allParams}} - {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}} = null; + //{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}} = null; {{/allParams}} - {{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{^vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}.block(){{/isArray}}{{^isArray}}.block(){{/isArray}}{{/vendorExtensions.x-webclient-blocking}}; + //{{#returnType}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{.}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{.}}}{{/isResponseFile}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{^vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}.block(){{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}.block(){{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}}; // TODO: test validations } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/ApiKeyAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/ApiKeyAuth.mustache index 857403b27..a5662c4a7 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/ApiKeyAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/ApiKeyAuth.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/Authentication.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/Authentication.mustache index 8e53c2ba0..70b1d1054 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/Authentication.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/Authentication.mustache @@ -1,14 +1,19 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; +{{>generatedAnnotation}} + public interface Authentication { /** * Apply authentication settings to header and / or query parameters. + * * @param queryParams The query parameters for the request * @param headerParams The header parameters for the request * @param cookieParams The cookie parameters for the request */ - public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams); + void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams, MultiValueMap cookieParams); } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBasicAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBasicAuth.mustache index 0194ccf07..4ee900963 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBasicAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBasicAuth.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import java.nio.charset.StandardCharsets; @@ -7,6 +9,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBearerAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBearerAuth.mustache index 353a8c7a5..ca1f5368f 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBearerAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/HttpBearerAuth.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class HttpBearerAuth implements Authentication { private final String scheme; private String bearerToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuth.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuth.mustache index 7889f1582..166475d1b 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuth.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuth.mustache @@ -1,9 +1,12 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; {{>generatedAnnotation}} + public class OAuth implements Authentication { private String accessToken; diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuthFlow.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuthFlow.mustache index 7ab35f6d8..759f354f5 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuthFlow.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/auth/OAuthFlow.mustache @@ -1,3 +1,5 @@ +{{>licenseInfo}} + package {{invokerPackage}}.auth; public enum OAuthFlow { diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/build.gradle.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/build.gradle.mustache index 5d97c7d0f..651004854 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/build.gradle.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/build.gradle.mustache @@ -32,8 +32,14 @@ if(hasProperty('target') && target == 'android') { } compileOptions { + {{#java17}} + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + {{/java17}} + {{^java17}} sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + {{/java17}} } // Rename the aar correctly @@ -57,16 +63,16 @@ if(hasProperty('target') && target == 'android') { def task = project.tasks.create "jar${variant.name.capitalize()}", Jar task.description = "Create jar artifact for ${variant.name}" task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - task.destinationDir = project.file("${project.buildDir}/outputs/jar") - task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + task.from variant.javaCompile.destinationDirectory + task.destinationDirectory = project.file("${project.buildDir}/outputs/jar") + task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar" artifacts.add('archives', task); } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs - classifier = 'sources' + archiveClassifier = 'sources' } artifacts { @@ -78,8 +84,14 @@ if(hasProperty('target') && target == 'android') { apply plugin: 'java' apply plugin: 'maven-publish' + {{#java17}} + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + {{/java17}} + {{^java17}} sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + {{/java17}} publishing { publications { @@ -91,17 +103,17 @@ if(hasProperty('target') && target == 'android') { } task execute(type:JavaExec) { - main = System.getProperty('mainClass') + mainClass = System.getProperty('mainClass') classpath = sourceSets.main.runtimeClasspath } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -112,22 +124,44 @@ if(hasProperty('target') && target == 'android') { } ext { + {{#swagger1AnnotationLibrary}} swagger_annotations_version = "1.6.3" - spring_boot_version = "2.6.6" - jackson_version = "2.13.4" - jackson_databind_version = "2.13.4.2" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} + {{#useJakartaEe}} + spring_boot_version = "3.2.12" + jakarta_annotation_version = "2.1.1" + beanvalidation_version = "3.0.2" + reactor_version = "3.5.12" + reactor_netty_version = "1.2.8" + {{/useJakartaEe}} + {{^useJakartaEe}} + spring_boot_version = "2.7.17" + jakarta_annotation_version = "1.3.5" + beanvalidation_version = "2.0.2" + reactor_version = "3.4.34" + reactor_netty_version = "1.2.8" + {{/useJakartaEe}} + jackson_version = "2.19.2" + jackson_databind_version = "2.19.2" {{#openApiNullable}} - jackson_databind_nullable_version = "0.2.4" + jackson_databind_nullable_version = "0.2.9" {{/openApiNullable}} - jakarta_annotation_version = "1.3.5" - reactor_version = "3.4.3" - reactor_netty_version = "1.0.4" + {{#joda}} jodatime_version = "2.9.9" - junit_version = "4.13.2" + {{/joda}} + junit_version = "5.10.2" } 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 "io.projectreactor:reactor-core:$reactor_version" implementation "org.springframework.boot:spring-boot-starter-webflux:$spring_boot_version" @@ -144,5 +178,5 @@ dependencies { {{/joda}} implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" - testImplementation "junit:junit:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version" } diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/model.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/model.mustache new file mode 100644 index 000000000..108748f60 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/model.mustache @@ -0,0 +1,78 @@ +{{>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}} +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/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pojo.mustache new file mode 100644 index 000000000..c110998f0 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pojo.mustache @@ -0,0 +1,630 @@ +/** + * {{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 {{>sealed}}class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{{>permits}}{ +{{#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}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{^isXmlAttribute}} + {{#isDateTime}} + @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) + {{/isDateTime}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{/gson}} + {{^isDiscriminator}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{/isDiscriminator}} + {{#isDiscriminator}} + // The discriminator does not have Nullability-annotation since it is added during serialization by the @JsonTypeName annotation + {{/isDiscriminator}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{.}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} 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}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{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}} + /** + * Constructor with only readonly parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} + */ + {{#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}} +{{#vendorExtensions.x-java-all-args-constructor}} + + /** + * Constructor with all args parameters + */ + public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { +{{#parent}} + super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); +{{/parent}} + {{#vars}} + 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}}; +{{/vars}} + } +{{/vendorExtensions.x-java-all-args-constructor}} + +{{#vars}} + {{^isReadOnly}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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{{nameInPascalCase}}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{{nameInPascalCase}}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}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#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}}({{>nullable_var_annotations}} {{{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}} +{{>libraries/webclient/additional_properties}} + + {{#parent}} + {{#readWriteVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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}} + {{/readWriteVars}} + {{/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}}{{#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({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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{{#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.datatypeWithEnum}}} _item : {{getter}}()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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}} +{{#generateBuilders}} + + {{>javaBuilder}}{{! prevent indent}} +{{/generateBuilders}} + +} diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pom.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pom.mustache index 38e4b653f..4fdc44379 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/pom.mustache @@ -43,22 +43,22 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.13.0 - {{#useJakartaEe}} + {{#java17}} 17 17 - {{/useJakartaEe}} - {{^useJakartaEe}} + {{/java17}} + {{^java17}} 1.8 1.8 - {{/useJakartaEe}} + {{/java17}} org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.3.1 attach-sources @@ -79,6 +79,13 @@ ${swagger-annotations-version} {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -146,35 +153,40 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api ${junit-version} test - - UTF-8 - 1.6.6 - 2.13.4 - 2.13.4.2 - {{#openApiNullable}} - 0.2.4 - {{/openApiNullable}} - {{#useJakartaEe}} - 3.0.1 - 2.1.1 - 3.5.1 - 1.1.1 - {{/useJakartaEe}} - {{^useJakartaEe}} - 2.6.6 - 1.3.5 - 3.4.3 - 1.0.4 - {{/useJakartaEe}} - 4.13.2 - {{#joda}} - 2.9.9 - {{/joda}} - + + UTF-8 + {{#swagger1AnnotationLibrary}} + 1.6.6 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 2.19.2 + 2.19.2 + {{#openApiNullable}} + 0.2.9 + {{/openApiNullable}} + {{#useJakartaEe}} + 3.2.12 + 2.1.1 + 3.5.12 + 1.2.8 + {{/useJakartaEe}} + {{^useJakartaEe}} + 2.7.17 + 1.3.5 + 3.4.34 + 1.2.8 + {{/useJakartaEe}} + 5.10.2 + {{#joda}} + 2.9.9 + {{/joda}} + diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/webclient/single_request_parameter.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/single_request_parameter.mustache new file mode 100644 index 000000000..1ea323aa9 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/libraries/webclient/single_request_parameter.mustache @@ -0,0 +1,106 @@ +{{#hasParams}} +{{^hasSingleParam}} + + public {{#staticRequest}}static {{/staticRequest}}class {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request { + {{#allParams}} + private {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}; + {{/allParams}} + + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request() {} + + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) { + {{#allParams}} + this.{{paramName}} = {{paramName}}; + {{/allParams}} + } + + {{#allParams}} + public {{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}() { + return this.{{paramName}}; + } + public {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request {{paramName}}({{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{>nullable_var_annotations}} {{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{>nullable_var_annotations}} {{{dataType}}}{{/isFile}} {{paramName}}) { + this.{{paramName}} = {{paramName}}; + return this; + } + + {{/allParams}} + @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; + }{{#hasParams}} + {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request request = ({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request) o; + return {{#allParams}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{paramName}}, request.{{paramName}}()){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{paramName}}, request.{{paramName}}()){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && + {{/-last}}{{/allParams}};{{/hasParams}}{{^hasParams}} + return true;{{/hasParams}} + {{/useReflectionEqualsHashCode}} + } + + @Override + public int hashCode() { + {{#useReflectionEqualsHashCode}} + return HashCodeBuilder.reflectionHashCode(this); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + return Objects.hash({{#allParams}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{paramName}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{paramName}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{paramName}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/useReflectionEqualsHashCode}} + } + } + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + {{#returnType}} * @return {{.}} + {{/returnType}} * @throws WebClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#returnType}}{{#vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Flux<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnBaseType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnBaseType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{^returnType}}{{#vendorExtensions.x-webclient-blocking}}void{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}Mono{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{operationId}}({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws WebClientResponseException { + {{^returnType}}{{^vendorExtensions.x-webclient-blocking}}return {{/vendorExtensions.x-webclient-blocking}}{{/returnType}}{{#returnType}}return {{/returnType}}this.{{operationId}}({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + {{#returnType}} * @return ResponseEntity<{{.}}> + {{/returnType}} * @throws WebClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity<{{#isResponseFile}}{{#useAbstractionForFiles}}org.springframework.core.io.Resource{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{returnType}}}{{/useAbstractionForFiles}}{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}ResponseEntity{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Mono>>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}Mono>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{operationId}}WithHttpInfo({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws WebClientResponseException { + return this.{{operationId}}WithHttpInfo({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + {{#responses}} *

{{code}}{{#message}} - {{.}}{{/message}} + {{/responses}} * @param requestParameters The {{operationId}} request parameters as object + * @return ResponseSpec + * @throws WebClientResponseException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public ResponseSpec {{operationId}}WithResponseSpec({{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request requestParameters) throws WebClientResponseException { + return this.{{operationId}}WithResponseSpec({{#allParams}}requestParameters.{{paramName}}(){{^-last}}, {{/-last}}{{/allParams}}); + } + +{{/hasSingleParam}} +{{/hasParams}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/licenseInfo.mustache b/boat-scaffold/src/main/templates/boat-java/licenseInfo.mustache index 635626f48..c66209f27 100644 --- a/boat-scaffold/src/main/templates/boat-java/licenseInfo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/licenseInfo.mustache @@ -2,10 +2,10 @@ * {{{appName}}} * {{{appDescription}}} * - * {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}} - * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}} * - * NOTE: This class is auto generated by OpenAPI Generator (https://github.com/Backbase/backbase-openapi-tools). - * https://github.com/Backbase/backbase-openapi-tools + * 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/boat-scaffold/src/main/templates/boat-java/maven.yml.mustache b/boat-scaffold/src/main/templates/boat-java/maven.yml.mustache index f3c4733c3..69ad41543 100644 --- a/boat-scaffold/src/main/templates/boat-java/maven.yml.mustache +++ b/boat-scaffold/src/main/templates/boat-java/maven.yml.mustache @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '8' ] + java: [ 17, 21 ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: {{=< >=}} java-version: ${{ matrix.java }} diff --git a/boat-scaffold/src/main/templates/boat-java/model.mustache b/boat-scaffold/src/main/templates/boat-java/model.mustache index 140d35fc3..0c3c6df51 100644 --- a/boat-scaffold/src/main/templates/boat-java/model.mustache +++ b/boat-scaffold/src/main/templates/boat-java/model.mustache @@ -17,6 +17,17 @@ import java.io.Serializable; {{#jackson}} import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonTypeName; +{{#models}} +{{#model}} +{{#additionalPropertiesType}} +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonFormat; +{{/additionalPropertiesType}} +{{/model}} +{{/models}} {{#withXml}} import com.fasterxml.jackson.dataformat.xml.annotation.*; {{/withXml}} @@ -25,23 +36,23 @@ import com.fasterxml.jackson.annotation.JsonCreator; {{/vendorExtensions.x-has-readonly-properties}} {{/jackson}} {{#withXml}} -import javax.xml.bind.annotation.*; -import javax.xml.bind.annotation.adapters.*; +import {{javaxPackage}}.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.adapters.*; import io.github.threetenjaxb.core.*; {{/withXml}} {{#jsonb}} import java.lang.reflect.Type; -import javax.json.bind.annotation.JsonbTypeDeserializer; -import javax.json.bind.annotation.JsonbTypeSerializer; -import javax.json.bind.serializer.DeserializationContext; -import javax.json.bind.serializer.JsonbDeserializer; -import javax.json.bind.serializer.JsonbSerializer; -import javax.json.bind.serializer.SerializationContext; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonParser; -import javax.json.bind.annotation.JsonbProperty; +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 javax.json.bind.annotation.JsonbCreator; +import {{javaxPackage}}.json.bind.annotation.JsonbCreator; {{/vendorExtensions.x-has-readonly-properties}} {{/jsonb}} {{#parcelableModel}} @@ -49,26 +60,20 @@ import android.os.Parcelable; import android.os.Parcel; {{/parcelableModel}} {{#useBeanValidation}} -{{^useJakartaEe}} -import javax.validation.constraints.*; -import javax.validation.Valid; -{{/useJakartaEe}} -{{#useJakartaEe}} -import jakarta.validation.constraints.*; -import jakarta.validation.Valid; -{{/useJakartaEe}} +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; +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}} \ No newline at end of file +{{/models}} diff --git a/boat-scaffold/src/main/templates/boat-java/modelEnum.mustache b/boat-scaffold/src/main/templates/boat-java/modelEnum.mustache index 2cfcb1cbf..3b54fddc1 100644 --- a/boat-scaffold/src/main/templates/boat-java/modelEnum.mustache +++ b/boat-scaffold/src/main/templates/boat-java/modelEnum.mustache @@ -9,10 +9,16 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; {{/gson}} +{{#isUri}} +import java.net.URI; +{{/isUri}} /** * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} */ +{{#isDeprecated}} +@Deprecated +{{/isDeprecated}} {{#gson}} @JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) {{/gson}} @@ -20,7 +26,7 @@ import com.google.gson.stream.JsonWriter; @JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) @JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) {{/jsonb}} -{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { +{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#allowableValues}}{{#enumVars}} {{#enumDescription}} /** @@ -33,7 +39,7 @@ import com.google.gson.stream.JsonWriter; {{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} - private final {{{dataType}}} value; + private {{{dataType}}} value; {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { this.value = value; @@ -56,7 +62,7 @@ import com.google.gson.stream.JsonWriter; {{/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)) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { return b; } } @@ -67,17 +73,18 @@ import com.google.gson.stream.JsonWriter; 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()); + jsonWriter.value(enumeration.getValue(){{#isUri}}.toASCIIString(){{/isUri}}); } @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}}); + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}}{{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/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) { @@ -100,17 +107,17 @@ import com.google.gson.stream.JsonWriter; {{#supportUrlQuery}} /** - * Convert the instance into URL query string. - * - * @param prefix prefix of the query string - * @return URL query string - */ + * 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 = ""; - } + if (prefix == null) { + prefix = ""; + } - return String.format("%s=%s", prefix, this.toString()); + return String.format(java.util.Locale.ROOT, "%s=%s", prefix, this.toString()); } {{/supportUrlQuery}} -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-java/modelInnerEnum.mustache b/boat-scaffold/src/main/templates/boat-java/modelInnerEnum.mustache index 93ca6279e..401002a69 100644 --- a/boat-scaffold/src/main/templates/boat-java/modelInnerEnum.mustache +++ b/boat-scaffold/src/main/templates/boat-java/modelInnerEnum.mustache @@ -12,7 +12,7 @@ @XmlType(name="{{datatypeWithEnum}}") @XmlEnum({{dataType}}.class) {{/withXml}} - {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} { + {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#allowableValues}} {{#enumVars}} {{#enumDescription}} @@ -23,12 +23,12 @@ {{#withXml}} @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{/withXml}} - {{{name}}}({{{value}}}){{^-last}}, + {{{name}}}({{^isUri}}{{dataType}}.valueOf({{/isUri}}{{{value}}}{{^isUri}}){{/isUri}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}} {{/enumVars}} {{/allowableValues}} - private final {{{dataType}}} value; + private {{{dataType}}} value; {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}({{{dataType}}} value) { this.value = value; @@ -51,7 +51,7 @@ {{/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)) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { return b; } } @@ -62,12 +62,12 @@ 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()); + jsonWriter.value(enumeration.getValue(){{#isUri}}.toASCIIString(){{/isUri}}); } @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}}; + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}} {{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); } } @@ -92,4 +92,4 @@ } } {{/jsonb}} - } \ No newline at end of file + } diff --git a/boat-scaffold/src/main/templates/boat-java/model_test.mustache b/boat-scaffold/src/main/templates/boat-java/model_test.mustache index 07468db56..931a5da03 100644 --- a/boat-scaffold/src/main/templates/boat-java/model_test.mustache +++ b/boat-scaffold/src/main/templates/boat-java/model_test.mustache @@ -4,21 +4,14 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -{{#fullJavaUtil}} -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -{{/fullJavaUtil}} +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; /** * Model tests for {{classname}} */ -public class {{classname}}Test { +class {{classname}}Test { {{#models}} {{#model}} {{^vendorExtensions.x-is-one-of-interface}} @@ -30,7 +23,7 @@ public class {{classname}}Test { * Model tests for {{classname}} */ @Test - public void test{{classname}}() { + void test{{classname}}() { // TODO: test {{classname}} } @@ -39,7 +32,7 @@ public class {{classname}}Test { * Test the property '{{name}}' */ @Test - public void {{name}}Test() { + void {{name}}Test() { // TODO: test {{name}} } diff --git a/boat-scaffold/src/main/templates/boat-java/nullable_var_annotations.mustache b/boat-scaffold/src/main/templates/boat-java/nullable_var_annotations.mustache new file mode 100644 index 000000000..7dbaf4029 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/nullable_var_annotations.mustache @@ -0,0 +1 @@ +{{#required}}{{#isNullable}}@{{javaxPackage}}.annotation.Nullable{{/isNullable}}{{^isNullable}}@{{javaxPackage}}.annotation.Nonnull{{/isNullable}}{{/required}}{{^required}}@{{javaxPackage}}.annotation.Nullable{{/required}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/oneof_interface.mustache b/boat-scaffold/src/main/templates/boat-java/oneof_interface.mustache index d67277274..07a7a8241 100644 --- a/boat-scaffold/src/main/templates/boat-java/oneof_interface.mustache +++ b/boat-scaffold/src/main/templates/boat-java/oneof_interface.mustache @@ -1,5 +1,8 @@ {{>additionalOneOfTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}} -public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} +public {{>sealed}}interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}}{{>permits}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/boat-scaffold/src/main/templates/boat-java/permits.mustache b/boat-scaffold/src/main/templates/boat-java/permits.mustache new file mode 100644 index 000000000..af0c9e6e3 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/permits.mustache @@ -0,0 +1 @@ +{{#useSealedOneOfInterfaces}}{{#vendorExtensions.x-is-one-of-interface}}{{#permits}}{{#-first}}permits {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{#-last}} {{/-last}}{{/permits}}{{/vendorExtensions.x-is-one-of-interface}}{{/useSealedOneOfInterfaces}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/pojo.mustache b/boat-scaffold/src/main/templates/boat-java/pojo.mustache index 27013bc68..0b5479df4 100644 --- a/boat-scaffold/src/main/templates/boat-java/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-java/pojo.mustache @@ -1,609 +1,629 @@ /** -* {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} - * @deprecated{{/isDeprecated}} -*/{{#isDeprecated}} - @Deprecated{{/isDeprecated}} + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */{{#isDeprecated}} +@Deprecated{{/isDeprecated}} {{#swagger1AnnotationLibrary}} - {{#description}} - @ApiModel(description = "{{{.}}}") - {{/description}} +{{#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}} +@JsonPropertyOrder({ +{{#vars}} + {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} +{{/vars}} +}) +{{#isClassnameSanitized}} +{{^hasDiscriminatorWithNonEmptyMapping}} +@JsonTypeName("{{name}}") +{{/hasDiscriminatorWithNonEmptyMapping}} +{{/isClassnameSanitized}} +{{#additionalPropertiesType}} +@JsonFormat(shape=JsonFormat.Shape.OBJECT) +{{/additionalPropertiesType}} {{/jackson}} {{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} {{#vendorExtensions.x-class-extra-annotation}} - {{{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}} + {{#vars}} + {{#isEnum}} {{^isContainer}} - {{>modelInnerEnum}} +{{>modelInnerEnum}} + {{/isContainer}} {{#isContainer}} - {{#mostInnerItems}} - {{>modelInnerEnum}} - {{/mostInnerItems}} + {{#mostInnerItems}} +{{>modelInnerEnum}} + + {{/mostInnerItems}} {{/isContainer}} - {{/isEnum}} + {{/isEnum}} {{#gson}} - public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; + public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; {{/gson}} {{#jackson}} - public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; + public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; {{/jackson}} {{#withXml}} - {{#isXmlAttribute}} - @XmlAttribute(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/isXmlAttribute}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} {{^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) + @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) {{/isDateTime}} {{/isXmlAttribute}} {{/withXml}} {{#gson}} - @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) {{/gson}} + {{^isDiscriminator}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{/isDiscriminator}} + {{#isDiscriminator}} + // The discriminator does not have Nullability-annotation since it is added during serialization by the @JsonTypeName annotation + {{/isDiscriminator}} {{#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}} + {{#isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} 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}} + {{#isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{^isContainer}} + {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} {{/vendorExtensions.x-is-jackson-optional-nullable}} -{{/vars}} -public {{classname}}() { -{{#parent}} - {{#parcelableModel}} + {{/vars}} + public {{classname}}() { + {{#parent}} + {{#parcelableModel}} super();{{/parcelableModel}} -{{/parent}} -{{#gson}} - {{#discriminator}} + {{/parent}} + {{#gson}} + {{#discriminator}} {{#discriminator.isEnum}} - this.{{{discriminatorName}}} = this.getClass().getSimpleName(); + this.{{{discriminatorName}}} = this.getClass().getSimpleName(); {{/discriminator.isEnum}} - {{/discriminator}} -{{/gson}} -} -{{#vendorExtensions.x-has-readonly-properties}} + {{/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}} - ) { + /** + * Constructor with only readonly parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} + */ + {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} + public {{classname}}( + {{#readOnlyVars}} + {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}, required = {{#isNullable}}false{{/isNullable}}{{^isNullable}}{{required}}{{/isNullable}}){{/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}} - } + {{#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}} + {{/vendorExtensions.x-has-readonly-properties}} +{{#vendorExtensions.x-java-all-args-constructor}} + /** + * Constructor with all args parameters + */ + public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}, required = {{#isNullable}}false{{/isNullable}}{{^isNullable}}{{required}}{{/isNullable}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { +{{#parent}} + super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); +{{/parent}} + {{#vars}} + 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}}; +{{/vars}} + } +{{/vendorExtensions.x-java-all-args-constructor}} + +{{#vars}} {{^isReadOnly}} - public {{classname}} {{#useWithModifiers}}with{{nameInCamelCase}}{{/useWithModifiers}}{{^useWithModifiers}}{{name}}{{/useWithModifiers}}({{{datatypeWithEnum}}} {{name}}) { + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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}} + } + {{#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}} add{{nameInPascalCase}}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}} + public {{classname}} put{{nameInPascalCase}}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}} + * Get {{name}} {{/description}} {{#minimum}} - * minimum: {{.}} + * minimum: {{.}} {{/minimum}} {{#maximum}} - * 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}} - {{#vendorExtensions.x-extra-annotation}} - {{{vendorExtensions.x-extra-annotation}}} - {{/vendorExtensions.x-extra-annotation}} - {{#vendorExtensions.x-is-jackson-optional-nullable}} + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ +{{#deprecated}} + @Deprecated +{{/deprecated}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#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}} + @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}} + {{#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) { + 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}} + {{/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}} } {{#vendorExtensions.x-is-jackson-optional-nullable}} - {{> jackson_annotations}} - public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { +{{> 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}}) { + @JsonProperty(value = JSON_PROPERTY_{{nameInSnakeCase}}, required = {{#isNullable}}false{{/isNullable}}{{^isNullable}}{{required}}{{/isNullable}}) + {{#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-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}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { {{#vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); {{/vendorExtensions.x-is-jackson-optional-nullable}} {{^vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = {{name}}; + this.{{name}} = {{name}}; {{/vendorExtensions.x-is-jackson-optional-nullable}} - } + } {{/isReadOnly}} -{{/vars}} -{{#parent}} - {{#allVars}} - {{#isOverridden}} - @Override - public {{classname}} {{#useWithModifiers}}with{{nameInCamelCase}}{{/useWithModifiers}}{{^useWithModifiers}}{{name}}{{/useWithModifiers}}({{{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; - } + {{/vars}} +{{>additional_properties}} - {{/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; + {{#parent}} + {{#readWriteVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{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; } - if (o == null || getClass() != o.getClass()) { - return false; - }{{#hasVars}} + + {{/isOverridden}} + {{/readWriteVars}} + {{/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}} + {{/-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}} + {{/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}} + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} @Override public int hashCode() { -{{#useReflectionEqualsHashCode}} + {{#useReflectionEqualsHashCode}} return HashCodeBuilder.reflectionHashCode(this); -{{/useReflectionEqualsHashCode}} -{{^useReflectionEqualsHashCode}} + {{/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}} + {{/useReflectionEqualsHashCode}} }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - private static int hashCodeNullable(JsonNullable a) { + private static int hashCodeNullable(JsonNullable a) { if (a == null) { - return 1; + return 1; } return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; - }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class {{classname}} {\n"); -{{#parent}} + 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(); + {{/parent}} + {{#vars}} + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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). - */ + * 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 "); + 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() { + /** + * 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) { + /** + * 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 = ""; + // 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 = "["; + // 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 + {{#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), + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.datatypeWithEnum}}} _item : {{getter}}()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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), + } 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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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.isPrimitiveType}} - 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) { + } 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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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); - } - } - } - {{/items.isPrimitiveType}} - {{^items.isPrimitiveType}} - 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.isPrimitiveType}} - {{/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) { + } + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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); - } - } - {{/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}} + } + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{^items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format(java.util.Locale.ROOT, "%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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}} + {{/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}} - } + 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}} - } +{{#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() { + public int describeContents() { return 0; - } + } - public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { public {{classname}} createFromParcel(Parcel in) { - {{#model}} - {{#isArray}} +{{#model}} +{{#isArray}} {{classname}} result = new {{classname}}(); - result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); - return result; - {{/isArray}} - {{^isArray}} - return new {{classname}}(in); - {{/isArray}} - {{/model}} + 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]; + return new {{classname}}[size]; } - }; + }; {{/parcelableModel}} +{{#generateBuilders}} - } + {{>javaBuilder}}{{! prevent indent}} +{{/generateBuilders}} + +} diff --git a/boat-scaffold/src/main/templates/boat-java/pojo_doc.mustache b/boat-scaffold/src/main/templates/boat-java/pojo_doc.mustache index 0b90946ba..bae0bc48c 100644 --- a/boat-scaffold/src/main/templates/boat-java/pojo_doc.mustache +++ b/boat-scaffold/src/main/templates/boat-java/pojo_doc.mustache @@ -34,4 +34,4 @@ {{#oneOf}} * {{{.}}} {{/oneOf}} -{{/vendorExtensions.x-is-one-of-interface}} \ No newline at end of file +{{/vendorExtensions.x-is-one-of-interface}} diff --git a/boat-scaffold/src/main/templates/boat-java/pom.mustache b/boat-scaffold/src/main/templates/boat-java/pom.mustache index 4920e245b..c46bc598e 100644 --- a/boat-scaffold/src/main/templates/boat-java/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-java/pom.mustache @@ -82,16 +82,24 @@ maven-surefire-plugin 2.12 - + loggerPath conf/log4j.properties - + -Xms512m -Xmx1500m methods pertest + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-version} + + maven-dependency-plugin @@ -214,11 +222,20 @@ + {{#swagger1AnnotationLibrary}} io.swagger swagger-annotations ${swagger-annotations-version} + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} @@ -256,19 +273,28 @@ jackson-databind ${jackson-databind-version} + {{^useJakartaEe}} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider ${jackson-version} + {{/useJakartaEe}} + {{#useJakartaEe}} + + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-json-provider + ${jackson-version} + + {{/useJakartaEe}} {{#withXml}} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${jackson-version} - + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + {{/withXml}} {{#joda}} @@ -315,31 +341,53 @@ ${jakarta-annotation-version} provided + {{#useReflectionEqualsHashCode}} + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + {{/useReflectionEqualsHashCode}} + - junit - junit + org.junit.jupiter + junit-jupiter-engine ${junit-version} test + + org.junit.platform + junit-platform-runner + ${junit-platform-runner.version} + test + - - UTF-8 - 1.6.6 - 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 - - \ No newline at end of file + + UTF-8 + {{#swagger1AnnotationLibrary}} + 1.6.6 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + 1.19.4 + 2.17.1 + 2.17.1 + {{#useJakartaEe}} + 2.1.1 + 3.0.2 + {{/useJakartaEe}} + {{^useJakartaEe}} + 1.3.5 + 2.0.2 + {{/useJakartaEe}} + {{#useReflectionEqualsHashCode}} + 3.17.0 + {{/useReflectionEqualsHashCode}} + 1.0.0 + 5.10.2 + 1.10.0 + + diff --git a/boat-scaffold/src/main/templates/boat-java/sealed.mustache b/boat-scaffold/src/main/templates/boat-java/sealed.mustache new file mode 100644 index 000000000..8e5076b49 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-java/sealed.mustache @@ -0,0 +1 @@ +{{#useSealedOneOfInterfaces}}{{#vendorExtensions.x-is-one-of-interface}}{{#permits.0}}sealed {{/permits.0}}{{/vendorExtensions.x-is-one-of-interface}}{{^permits.0}}{{#vendorExtensions.x-implements}}final {{/vendorExtensions.x-implements}}{{/permits.0}}{{/useSealedOneOfInterfaces}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-java/travis.mustache b/boat-scaffold/src/main/templates/boat-java/travis.mustache index e3bdf2af1..1b6741c08 100644 --- a/boat-scaffold/src/main/templates/boat-java/travis.mustache +++ b/boat-scaffold/src/main/templates/boat-java/travis.mustache @@ -18,5 +18,5 @@ script: #- mvn test # test using gradle - gradle test - # test using sbt + # test using sbt # - sbt test diff --git a/boat-scaffold/src/main/templates/boat-java/typeInfoAnnotation.mustache b/boat-scaffold/src/main/templates/boat-java/typeInfoAnnotation.mustache index 3777e48ce..23c0dbee7 100644 --- a/boat-scaffold/src/main/templates/boat-java/typeInfoAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-java/typeInfoAnnotation.mustache @@ -1,11 +1,15 @@ {{#jackson}} +{{^disableDiscriminatorJsonIgnoreProperties}} @JsonIgnoreProperties( value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization - allowGetters = true, // allows the {{{discriminator.propertyBaseName}}} to be set 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) +{{/disableDiscriminatorJsonIgnoreProperties}} +{{#disableDiscriminatorJsonIgnoreProperties}} +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true) +{{/disableDiscriminatorJsonIgnoreProperties}} {{#discriminator.mappedModels}} {{#-first}} @JsonSubTypes({ @@ -15,4 +19,10 @@ }) {{/-last}} {{/discriminator.mappedModels}} -{{/jackson}} \ No newline at end of file +{{/jackson}} +{{#jsonbPolymorphism}} +@JsonbTypeInfo(key = "{{{discriminator.propertyBaseName}}}"{{#discriminator.mappedModels}}{{#-first}}, value = { +{{/-first}} + @JsonbSubtype(alias = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}", type = {{modelName}}.class), +{{#-last}} +}{{/-last}}{{/discriminator.mappedModels}}){{/jsonbPolymorphism}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/additionalModelTypeAnnotations.mustache b/boat-scaffold/src/main/templates/boat-spring/additionalModelTypeAnnotations.mustache index f4871c02c..91b4950fd 100644 --- a/boat-scaffold/src/main/templates/boat-spring/additionalModelTypeAnnotations.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/additionalModelTypeAnnotations.mustache @@ -1,2 +1,3 @@ -{{#additionalModelTypeAnnotations}}{{{.}}} +{{#additionalModelTypeAnnotations}} +{{{.}}} {{/additionalModelTypeAnnotations}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/additionalProperties.mustache b/boat-scaffold/src/main/templates/boat-spring/additionalProperties.mustache new file mode 100644 index 000000000..649734535 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/additionalProperties.mustache @@ -0,0 +1,39 @@ +{{#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. + */ + @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) property. + */ + @JsonAnyGetter + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * 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}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/api.mustache b/boat-scaffold/src/main/templates/boat-spring/api.mustache index c2df81c48..7f396c340 100644 --- a/boat-scaffold/src/main/templates/boat-spring/api.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/api.mustache @@ -1,51 +1,62 @@ /* -Boat Generator configuration: - useBeanValidation: {{useBeanValidation}} - useOptional: {{useOptional}} - addServletRequest: {{addServletRequest}} - addBindingResult: {{addBindingResult}} - useLombokAnnotations: {{useLombokAnnotations}} - openApiNullable: {{openApiNullable}} - useSetForUniqueItems: {{useSetForUniqueItems}} - useWithModifiers: {{useWithModifiers}} -*/ + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) ({{{generatorVersion}}}). + * https://openapi-generator.tech + * Do not edit the class manually. + */ package {{package}}; {{#imports}}import {{import}}; {{/imports}} {{#swagger2AnnotationLibrary}} +import io.swagger.v3.oas.annotations.ExternalDocumentation; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.ExampleObject; {{/swagger2AnnotationLibrary}} {{#swagger1AnnotationLibrary}} import io.swagger.annotations.*; {{/swagger1AnnotationLibrary}} -{{#jdk8-no-delegate}} +{{^isDelegate}} +{{#jdk8-default-interface}} {{#virtualService}} import io.virtualan.annotation.ApiVirtual; import io.virtualan.annotation.VirtualService; {{/virtualService}} import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -{{/jdk8-no-delegate}} +{{/jdk8-default-interface}} +{{/isDelegate}} +{{^useResponseEntity}} +import org.springframework.http.HttpStatus; +{{/useResponseEntity}} +{{#useResponseEntity}} import org.springframework.http.ResponseEntity; -{{#useBeanValidation}} -import org.springframework.validation.annotation.Validated; -{{/useBeanValidation}} +{{/useResponseEntity}} +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}import org.springframework.validation.annotation.Validated;{{/useSpringBuiltInValidation}}{{/useBeanValidation}} {{#useSpringController}} +{{#useResponseEntity}} import org.springframework.stereotype.Controller; +{{/useResponseEntity}} +{{^useResponseEntity}} +import org.springframework.web.bind.annotation.RestController; +{{/useResponseEntity}} {{/useSpringController}} import org.springframework.web.bind.annotation.*; -{{#jdk8-no-delegate}} -{{^reactive}} +{{#jdk8-default-interface}} + {{^isDelegate}} + {{^reactive}} import org.springframework.web.context.request.NativeWebRequest; -{{/reactive}} -{{/jdk8-no-delegate}} + {{/reactive}} + {{/isDelegate}} +{{/jdk8-default-interface}} import org.springframework.web.multipart.MultipartFile; {{#reactive}} import org.springframework.web.server.ServerWebExchange; @@ -55,60 +66,43 @@ import org.springframework.http.codec.multipart.Part; {{/reactive}} {{#useBeanValidation}} -{{#useJakartaEe}} -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.validation.Valid; -import javax.validation.constraints.*; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; {{/useBeanValidation}} -{{#addServletRequest}} -{{^useJakartaEe}} -import javax.servlet.http.HttpServletRequest; -{{/useJakartaEe}} -{{#useJakartaEe}} -import jakarta.servlet.http.HttpServletRequest; -{{/useJakartaEe}} -{{/addServletRequest}} -{{#addBindingResult}} -import org.springframework.validation.BindingResult; -{{/addBindingResult}} import java.util.List; import java.util.Map; -{{#jdk8-no-delegate}} +{{^isDelegate}} import java.util.Optional; -{{/jdk8-no-delegate}} -{{^jdk8-no-delegate}} +{{/isDelegate}} +{{#isDelegate}} {{#useOptional}} import java.util.Optional; {{/useOptional}} -{{/jdk8-no-delegate}} +{{/isDelegate}} {{#async}} import java.util.concurrent.CompletableFuture; {{/async}} -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +{{#returnSuccessCode}} +import java.util.concurrent.atomic.AtomicInteger; +{{/returnSuccessCode}} +import {{javaxPackage}}.annotation.Generated; {{>generatedAnnotation}} -{{#useBeanValidation}} -{{#useClassLevelBeanValidation}} -@Validated -{{/useClassLevelBeanValidation}} -{{/useBeanValidation}} + +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}@Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} {{#useSpringController}} +{{#useResponseEntity}} @Controller +{{/useResponseEntity}} +{{^useResponseEntity}} +@RestController +{{/useResponseEntity}} {{/useSpringController}} {{#swagger2AnnotationLibrary}} -@io.swagger.v3.oas.annotations.tags.Tag(name = "{{{baseName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{baseName}}} API"{{/tagDescription}}) +@Tag(name = "{{{tagName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{tagName}}} API"{{/tagDescription}}) {{/swagger2AnnotationLibrary}} {{#swagger1AnnotationLibrary}} -@Api(value = "{{{baseName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{baseName}}} API"{{/tagDescription}}) +@Api(value = "{{{tagName}}}", description = {{#tagDescription}}"{{{.}}}"{{/tagDescription}}{{^tagDescription}}"the {{{tagName}}} API"{{/tagDescription}}) {{/swagger1AnnotationLibrary}} {{#operations}} {{#virtualService}} @@ -138,6 +132,7 @@ public interface {{classname}} { {{/jdk8-default-interface}} {{#operation}} + String PATH_{{#lambda.uppercase}}{{#lambda.snakecase}}{{{operationId}}}{{/lambda.snakecase}}{{/lambda.uppercase}} = "{{{path}}}"; /** * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}} {{#notes}} @@ -172,23 +167,39 @@ public interface {{classname}} { {{#notes}} description = "{{{.}}}", {{/notes}} + {{#isDeprecated}} + deprecated = true, + {{/isDeprecated}} {{#vendorExtensions.x-tags.size}} tags = { {{#vendorExtensions.x-tags}}"{{tag}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-tags}} }, {{/vendorExtensions.x-tags.size}} responses = { {{#responses}} - @ApiResponse(responseCode = "{{{code}}}", description = "{{{message}}}"{{#baseType}}, content = { + @ApiResponse(responseCode = {{#isDefault}}"default"{{/isDefault}}{{^isDefault}}"{{{code}}}"{{/isDefault}}, description = "{{{message}}}"{{#baseType}}, content = { {{#produces}} - @Content(mediaType = "{{{mediaType}}}", schema = @Schema(implementation = {{{baseType}}}.class)){{^-last}},{{/-last}} + @Content(mediaType = "{{{mediaType}}}", {{#isArray}}array = @ArraySchema({{/isArray}}schema = @Schema(implementation = {{{baseType}}}.class){{#isArray}}){{/isArray}}{{^isJson}}){{^-last}},{{/-last}}{{/isJson}}{{#isJson}}{{^examples.0}}){{^-last}},{{/-last}}{{/examples.0}}{{#examples.0}}, examples = { + {{#examples}} + @ExampleObject( + name = "{{{exampleName}}}", + value = "{{{exampleValue}}}" + ){{^-last}},{{/-last}} + {{/examples}} + {{#-last}} + }) + {{/-last}} + {{^-last}} + }), + {{/-last}}{{/examples.0}}{{/isJson}} {{/produces}} }{{/baseType}}){{^-last}},{{/-last}} {{/responses}} }{{#hasAuthMethods}}, security = { {{#authMethods}} - @SecurityRequirement(name = "{{name}}"{{#isOAuth}}, scopes={ {{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}} }{{/isOAuth}}){{^-last}},{{/-last}} + @SecurityRequirement(name = "{{name}}"{{#scopes.0}}, scopes={ {{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}} }{{/scopes.0}}){{^-last}},{{/-last}} {{/authMethods}} - }{{/hasAuthMethods}} + }{{/hasAuthMethods}}{{#externalDocs}}, + externalDocs = @ExternalDocumentation(description = "{{externalDocs.description}}", url = "{{externalDocs.url}}"){{/externalDocs}} ) {{/swagger2AnnotationLibrary}} {{#swagger1AnnotationLibrary}} @@ -203,16 +214,16 @@ public interface {{classname}} { responseContainer = "{{{.}}}"{{/returnContainer}}{{#hasAuthMethods}}, authorizations = { {{#authMethods}} - {{#isOAuth}} + {{#scopes.0}} @Authorization(value = "{{name}}", scopes = { {{#scopes}} @AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{^-last}},{{/-last}} {{/scopes}} }){{^-last}},{{/-last}} - {{/isOAuth}} - {{^isOAuth}} + {{/scopes.0}} + {{^scopes.0}} @Authorization(value = "{{name}}"){{^-last}},{{/-last}} - {{/isOAuth}} + {{/scopes.0}} {{/authMethods}} }{{/hasAuthMethods}} ) @ApiResponses({ @@ -239,30 +250,44 @@ public interface {{classname}} { {{/implicitHeadersParams.0}} @RequestMapping( method = RequestMethod.{{httpMethod}}, - value = "{{{path}}}"{{#singleContentTypes}}{{#hasProduces}}, - produces = "{{{vendorExtensions.x-accepts}}}"{{/hasProduces}}{{#hasConsumes}}, + value = {{classname}}.PATH_{{#lambda.uppercase}}{{#lambda.snakecase}}{{{operationId}}}{{/lambda.snakecase}}{{/lambda.uppercase}}{{#singleContentTypes}}{{#hasProduces}}, + produces = { {{#vendorExtensions.x-accepts}}"{{{.}}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-accepts}} }{{/hasProduces}}{{#hasConsumes}}, consumes = "{{{vendorExtensions.x-content-type}}}"{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}, produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}}, - consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}} + consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}}, + headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}}, + params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}}{{^empty}}, + version = "{{{vendorExtensions.x-spring-api-version}}}"{{/empty}}{{/vendorExtensions.x-spring-api-version}} ) + {{^useResponseEntity}} + @ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}}) + {{/useResponseEntity}} + {{#vendorExtensions.x-operation-extra-annotation}} + {{{.}}} + {{/vendorExtensions.x-operation-extra-annotation}} + {{#vendorExtensions.x-sse}} + @ResponseBody + {{/vendorExtensions.x-sse}} {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}( - {{#allParams}}{{#toOneLine}}{{>queryParams}}{{/toOneLine}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{>httpServletParam}}{{^-last}}, + {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, - {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}} + {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.x-spring-provide-args}}{{#hasParams}}, + {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} {{{.}}}{{^hasParams}}{{^-last}}{{^reactive}},{{/reactive}} + {{/-last}}{{/hasParams}}{{/vendorExtensions.x-spring-provide-args}} ){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { {{#delegate-method}} - return {{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}, pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); } // Override this method - {{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}, {{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} { + {{#jdk8-default-interface}}default {{/jdk8-default-interface}} {{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}} { {{/delegate-method}} {{^isDelegate}} - {{>methodBody}} + {{>methodBody}}{{! prevent indent}} {{/isDelegate}} {{#isDelegate}} - return getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}, pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); {{/isDelegate}} }{{/jdk8-default-interface}} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiController.mustache b/boat-scaffold/src/main/templates/boat-spring/apiController.mustache index 21bec1775..47e4a7b73 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiController.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiController.mustache @@ -21,8 +21,13 @@ import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +{{#useResponseEntity}} import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +{{/useResponseEntity}} +{{^useResponseEntity}} +import org.springframework.web.bind.annotation.RestController; +{{/useResponseEntity}} import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -36,28 +41,23 @@ import org.springframework.web.context.request.NativeWebRequest; {{/isDelegate}} {{#useBeanValidation}} -{{#useJakartaEe}} -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.validation.constraints.*; -import javax.validation.Valid; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; {{/useBeanValidation}} import java.util.List; import java.util.Map; import java.util.Optional; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; {{>generatedAnnotation}} + +{{#useResponseEntity}} @Controller +{{/useResponseEntity}} +{{^useResponseEntity}} +@RestController +{{/useResponseEntity}} {{#useRequestMappingOnController}} {{=<% %>=}} @RequestMapping("${openapi.<%title%>.base-path:<%>defaultBasePath%>}") @@ -120,30 +120,30 @@ public class {{classname}}Controller implements {{classname}} { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - public {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}( + public {{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}( {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, - {{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}} + {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}} ) { {{^isDelegate}} {{^async}} - {{>methodBody}} + {{>methodBody}}{{! prevent indent}} {{/async}} {{#async}} - return new CallablereturnTypes}}>>() { + return new Callable<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}>() { @Override - public ResponseEntity<{{>returnTypes}}> call() { - {{>methodBody}} + public {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} call() { + {{>methodBody}}{{! prevent indent}} } }; {{/async}} {{/isDelegate}} {{#isDelegate}} - return delegate.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}, pageable{{/vendorExtensions.x-spring-paginated}}); + {{^isVoid}}return {{/isVoid}}{{#isVoid}}{{#useResponseEntity}}return {{/useResponseEntity}}{{^useResponseEntity}}{{#reactive}}return {{/reactive}}{{/useResponseEntity}}{{/isVoid}}delegate.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}}); {{/isDelegate}} } {{/operation}} {{/_api_controller_impl_}} } -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiDelegate.mustache b/boat-scaffold/src/main/templates/boat-spring/apiDelegate.mustache index 222556bab..44d989952 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiDelegate.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiDelegate.mustache @@ -1,20 +1,12 @@ -/* -Boat Generator configuration: - useBeanValidation: {{useBeanValidation}} - useOptional: {{useOptional}} - addServletRequest: {{addServletRequest}} - useLombokAnnotations: {{useLombokAnnotations}} - openApiNullable: {{openApiNullable}} - useSetForUniqueItems: {{useSetForUniqueItems}} - useWithModifiers: {{useWithModifiers}} -*/ package {{package}}; {{#imports}}import {{import}}; {{/imports}} import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +{{#useResponseEntity}} import org.springframework.http.ResponseEntity; +{{/useResponseEntity}} import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; {{#reactive}} @@ -24,18 +16,20 @@ import reactor.core.publisher.Mono; import org.springframework.http.codec.multipart.Part; {{/reactive}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} import java.util.List; import java.util.Map; import java.util.Optional; {{#async}} import java.util.concurrent.CompletableFuture; {{/async}} -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +{{#returnSuccessCode}} +import java.util.concurrent.atomic.AtomicInteger; +{{/returnSuccessCode}} +import {{javaxPackage}}.annotation.Generated; {{#operations}} /** @@ -43,27 +37,9 @@ import javax.annotation.Generated; * Implement this interface with a {@link org.springframework.stereotype.Service} annotated class. */ {{>generatedAnnotation}} + public interface {{classname}}Delegate { {{#jdk8-default-interface}} -{{^useApiUtil}} -{{^reactive}} - static void setExampleResponse(NativeWebRequest req, String contentType, String example) { - try { - HttpServletResponse res = req.getNativeResponse(HttpServletResponse.class); - res.setCharacterEncoding("UTF-8"); - res.addHeader("Content-Type", contentType); - res.getWriter().print(example); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -{{/reactive}} -{{#reactive}} - static Mono getExampleResponse(ServerWebExchange exchange, String example) { - return exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(example.getBytes(StandardCharsets.UTF_8)))); - } -{{/reactive}} -{{/useApiUtil}} default Optional getRequest() { return Optional.empty(); @@ -94,12 +70,12 @@ public interface {{classname}}Delegate { {{#isDeprecated}} @Deprecated {{/isDeprecated}} - {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#isArray}}List<{{/isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}}{{/isFile}} {{paramName}}{{^-last}}, + {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{operationId}}({{#allParams}}{{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{/isFile}}{{#isFile}}{{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}{{#isFormParam}}MultipartFile{{/isFormParam}}{{^isFormParam}}{{>optionalDataType}}{{/isFormParam}}{{#isArray}}>{{/isArray}}{{/reactive}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, - {{/hasParams}}ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}, final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { - {{>methodBody}} + {{/hasParams}}ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}}, {{/reactive}}{{/hasParams}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}){{#unhandledException}} throws Exception{{/unhandledException}}{{^jdk8-default-interface}};{{/jdk8-default-interface}}{{#jdk8-default-interface}} { + {{>methodBody}}{{! prevent indent}} }{{/jdk8-default-interface}} {{/operation}} } -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiException.mustache b/boat-scaffold/src/main/templates/boat-spring/apiException.mustache index 0464ec523..57f2e27c0 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiException.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiException.mustache @@ -1,16 +1,12 @@ package {{apiPackage}}; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; /** * The exception that can be used to store the HTTP status code returned by an API response. */ {{>generatedAnnotation}} + public class ApiException extends Exception { /** The HTTP status code. */ private int code; @@ -41,4 +37,4 @@ public class ApiException extends Exception { "code=" + code + '}'; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiOriginFilter.mustache b/boat-scaffold/src/main/templates/boat-spring/apiOriginFilter.mustache index 688592a14..b4126a8ff 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiOriginFilter.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiOriginFilter.mustache @@ -2,19 +2,13 @@ package {{apiPackage}}; import java.io.IOException; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletResponse; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -import javax.servlet.*; -import javax.servlet.http.HttpServletResponse; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; +import {{javaxPackage}}.servlet.*; +import {{javaxPackage}}.servlet.http.HttpServletResponse; {{>generatedAnnotation}} -public class ApiOriginFilter implements Filter { + +public class ApiOriginFilter implements {{javaxPackage}}.servlet.Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -32,4 +26,4 @@ public class ApiOriginFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiResponseMessage.mustache b/boat-scaffold/src/main/templates/boat-spring/apiResponseMessage.mustache index af92021f3..151dc0efe 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiResponseMessage.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiResponseMessage.mustache @@ -1,15 +1,11 @@ package {{apiPackage}}; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} -import javax.xml.bind.annotation.XmlTransient; +import {{javaxPackage}}.annotation.Generated; +import {{javaxPackage}}.xml.bind.annotation.XmlTransient; {{>generatedAnnotation}} -@javax.xml.bind.annotation.XmlRootElement + +@{{javaxPackage}}.xml.bind.annotation.XmlRootElement public class ApiResponseMessage { public static final int ERROR = 1; public static final int WARNING = 2; @@ -72,4 +68,4 @@ public class ApiResponseMessage { public void setMessage(String message) { this.message = message; } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/apiUtil.mustache b/boat-scaffold/src/main/templates/boat-spring/apiUtil.mustache index 94bd39dd4..eafd83316 100644 --- a/boat-scaffold/src/main/templates/boat-spring/apiUtil.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/apiUtil.mustache @@ -12,12 +12,7 @@ import reactor.core.publisher.Mono; {{^reactive}} import org.springframework.web.context.request.NativeWebRequest; -{{#useJakartaEe}} -import jakarta.servlet.http.HttpServletResponse; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.servlet.http.HttpServletResponse; -{{/useJakartaEe}} +import {{javaxPackage}}.servlet.http.HttpServletResponse; import java.io.IOException; {{/reactive}} @@ -26,9 +21,11 @@ public class ApiUtil { public static void setExampleResponse(NativeWebRequest req, String contentType, String example) { try { HttpServletResponse res = req.getNativeResponse(HttpServletResponse.class); - res.setCharacterEncoding("UTF-8"); - res.addHeader("Content-Type", contentType); - res.getWriter().print(example); + if (res != null) { + res.setCharacterEncoding("UTF-8"); + res.addHeader("Content-Type", contentType); + res.getWriter().print(example); + } } catch (IOException e) { throw new RuntimeException(e); } @@ -44,4 +41,4 @@ public class ApiUtil { return response.writeWith(Mono.just(data)); } {{/reactive}} -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache b/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache index e427a43a0..0aed4a92d 100644 --- a/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache @@ -1 +1 @@ -{{#required}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/required}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}@Valid {{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}@Valid {{/isPrimitiveType}}{{/isContainer}}{{>beanValidationCore}} \ No newline at end of file +{{#required}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/required}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}@Valid {{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}@Valid {{/isPrimitiveType}}{{/isContainer}}{{^openApiNullable}}{{>beanValidationCore}}{{/openApiNullable}}{{#openApiNullable}}{{^useOptional}}{{>beanValidationCore}}{{/useOptional}}{{/openApiNullable}}{{#useOptional}}{{#openApiNullable}}{{#isContainer}}{{^required}}{{>beanValidationCore}}{{/required}}{{/isContainer}}{{/openApiNullable}}{{#openApiNullable}}{{#required}}{{>beanValidationCore}}{{/required}}{{/openApiNullable}}{{/useOptional}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/beanValidationCore.mustache b/boat-scaffold/src/main/templates/boat-spring/beanValidationCore.mustache index af85b9dd3..ce71a6883 100644 --- a/boat-scaffold/src/main/templates/boat-spring/beanValidationCore.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/beanValidationCore.mustache @@ -1,25 +1,24 @@ -{{#vendorExtensions.x-not-null}}@NotNull{{/vendorExtensions.x-not-null}} -{{#pattern}}{{^isByteArray}}@Pattern(regexp = "{{{pattern}}}") {{/isByteArray}}{{/pattern}}{{! +{{^isUuid}}{{#pattern}}{{^isByteArray}}@Pattern(regexp = "{{{pattern}}}"{{#vendorExtensions.x-pattern-message}}, message = "{{vendorExtensions.x-pattern-message}}"{{/vendorExtensions.x-pattern-message}}) {{/isByteArray}}{{/pattern}}{{! minLength && maxLength set -}}{{#minLength}}{{#maxLength}}@Size(min = {{minLength}}, max = {{maxLength}}) {{/maxLength}}{{/minLength}}{{! +}}{{#minLength}}{{#maxLength}}@Size(min = {{minLength}}, max = {{maxLength}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxLength}}{{/minLength}}{{! minLength set, maxLength not -}}{{#minLength}}{{^maxLength}}@Size(min = {{minLength}}) {{/maxLength}}{{/minLength}}{{! +}}{{#minLength}}{{^maxLength}}@Size(min = {{minLength}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxLength}}{{/minLength}}{{! minLength not set, maxLength set -}}{{^minLength}}{{#maxLength}}@Size(max = {{.}}) {{/maxLength}}{{/minLength}}{{! +}}{{^minLength}}{{#maxLength}}@Size(max = {{.}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxLength}}{{/minLength}}{{! @Size: minItems && maxItems set -}}{{#minItems}}{{#maxItems}}@Size(min = {{minItems}}, max = {{maxItems}}) {{/maxItems}}{{/minItems}}{{! +}}{{#minItems}}{{#maxItems}}@Size(min = {{minItems}}, max = {{maxItems}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxItems}}{{/minItems}}{{! @Size: minItems set, maxItems not -}}{{#minItems}}{{^maxItems}}@Size(min = {{minItems}}) {{/maxItems}}{{/minItems}}{{! +}}{{#minItems}}{{^maxItems}}@Size(min = {{minItems}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxItems}}{{/minItems}}{{! @Size: minItems not set && maxItems set -}}{{^minItems}}{{#maxItems}}@Size(max = {{.}}) {{/maxItems}}{{/minItems}}{{! -@Email: useBeanValidation set && isEmail && java8 set -}}{{#useBeanValidation}}{{#isEmail}}{{#java8}}@org.hibernate.validator.constraints.Email{{/java8}}{{/isEmail}}{{/useBeanValidation}}{{! -@Email: performBeanValidation set && isEmail && not java8 set -}}{{#performBeanValidation}}{{#isEmail}}{{^java8}}@jakarta.validation.constraints.Email{{/java8}}{{/isEmail}}{{/performBeanValidation}}{{! +}}{{^minItems}}{{#maxItems}}@Size(max = {{.}}{{#vendorExtensions.x-size-message}}, message = "{{vendorExtensions.x-size-message}}"{{/vendorExtensions.x-size-message}}) {{/maxItems}}{{/minItems}}{{! +@Email: useBeanValidation +}}{{#isEmail}}{{#useBeanValidation}}@{{javaxPackage}}.validation.constraints.Email {{/useBeanValidation}}{{! +@Email: performBeanValidation exclusive +}}{{^useBeanValidation}}{{#performBeanValidation}}@org.hibernate.validator.constraints.Email {{/performBeanValidation}}{{/useBeanValidation}}{{/isEmail}}{{! check for integer or long / all others=decimal type with @Decimal* isInteger set -}}{{#isInteger}}{{#minimum}}@Min({{.}}) {{/minimum}}{{#maximum}}@Max({{.}}) {{/maximum}}{{/isInteger}}{{! +}}{{#isInteger}}{{#minimum}}@Min(value = {{.}}{{#vendorExtensions.x-minimum-message}}, message = "{{vendorExtensions.x-minimum-message}}"{{/vendorExtensions.x-minimum-message}}) {{/minimum}}{{#maximum}}@Max(value = {{.}}{{#vendorExtensions.x-maximum-message}}, message = "{{vendorExtensions.x-maximum-message}}"{{/vendorExtensions.x-maximum-message}}) {{/maximum}}{{/isInteger}}{{! isLong set -}}{{#isLong}}{{#minimum}}@Min({{.}}L) {{/minimum}}{{#maximum}}@Max({{.}}L) {{/maximum}}{{/isLong}}{{! +}}{{#isLong}}{{#minimum}}@Min(value = {{.}}L{{#vendorExtensions.x-minimum-message}}, message = "{{vendorExtensions.x-minimum-message}}"{{/vendorExtensions.x-minimum-message}}) {{/minimum}}{{#maximum}}@Max(value = {{.}}L{{#vendorExtensions.x-maximum-message}}, message = "{{vendorExtensions.x-maximum-message}}"{{/vendorExtensions.x-maximum-message}}) {{/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 +}}{{^isInteger}}{{^isLong}}{{#minimum}}@DecimalMin(value = "{{minimum}}"{{#exclusiveMinimum}}, inclusive = false{{/exclusiveMinimum}}{{#vendorExtensions.x-minimum-message}}, message = "{{vendorExtensions.x-minimum-message}}"{{/vendorExtensions.x-minimum-message}}) {{/minimum}}{{#maximum}}@DecimalMax(value = "{{maximum}}"{{#exclusiveMaximum}}, inclusive = false{{/exclusiveMaximum}}{{#vendorExtensions.x-maximum-message}}, message = "{{vendorExtensions.x-maximum-message}}"{{/vendorExtensions.x-maximum-message}}) {{/maximum}}{{/isLong}}{{/isInteger}}{{/isUuid}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/beanValidationPathParams.mustache b/boat-scaffold/src/main/templates/boat-spring/beanValidationPathParams.mustache index 051bd53c0..84b680524 100644 --- a/boat-scaffold/src/main/templates/boat-spring/beanValidationPathParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/beanValidationPathParams.mustache @@ -1 +1 @@ -{{! PathParam is always required, no @NotNull necessary }}{{>beanValidationCore}} \ No newline at end of file +{{! PathParam is always required }}@NotNull {{>beanValidationCore}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache b/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache index 42ca93d70..ea2019e65 100644 --- a/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache @@ -1 +1 @@ -{{#isBodyParam}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{>beanValidationBodyParams}}{{/useBeanValidation}} @RequestBody{{^required}}(required = false){{/required}} {{^reactive}}{{{dataType}}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}} {{paramName}}{{#useBeanValidation}}{{#addBindingResult}}, BindingResult bindingResult{{/addBindingResult}}{{/useBeanValidation}}{{/isBodyParam}} \ No newline at end of file +{{#isBodyParam}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{>beanValidationBodyParams}}{{/useBeanValidation}} @RequestBody{{^required}}(required = false){{/required}} {{^reactive}}{{>nullableAnnotation}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isBodyParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/converter.mustache b/boat-scaffold/src/main/templates/boat-spring/converter.mustache index a331ded63..4578768dd 100644 --- a/boat-scaffold/src/main/templates/boat-spring/converter.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/converter.mustache @@ -3,7 +3,7 @@ package {{configPackage}}; {{#models}} {{#model}} {{#isEnum}} -import {{modelPackage}}.{{name}}; +import {{modelPackage}}.{{classname}}; {{/isEnum}} {{/model}} {{/models}} @@ -12,18 +12,25 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; -@Configuration +/** + * This class provides Spring Converter beans for the enum models in the OpenAPI specification. + * + * By default, Spring only converts primitive types to enums using Enum::valueOf, which can prevent + * correct conversion if the OpenAPI specification is using an `enumPropertyNaming` other than + * `original` or the specification has an integer enum. + */ +@Configuration(value = "{{configPackage}}.enumConverterConfiguration") public class EnumConverterConfiguration { {{#models}} {{#model}} {{#isEnum}} - @Bean - Converter<{{{dataType}}}, {{name}}> {{classVarName}}Converter() { - return new Converter<{{{dataType}}}, {{name}}>() { + @Bean(name = "{{configPackage}}.EnumConverterConfiguration.{{classVarName}}Converter") + Converter<{{{dataType}}}, {{classname}}> {{classVarName}}Converter() { + return new Converter<{{{dataType}}}, {{classname}}>() { @Override - public {{name}} convert({{{dataType}}} source) { - return {{name}}.fromValue(source); + public {{classname}} convert({{{dataType}}} source) { + return {{classname}}.fromValue(source); } }; } diff --git a/boat-scaffold/src/main/templates/boat-spring/cookieParams.mustache b/boat-scaffold/src/main/templates/boat-spring/cookieParams.mustache index 74a837988..a255b5c7d 100644 --- a/boat-scaffold/src/main/templates/boat-spring/cookieParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/cookieParams.mustache @@ -1 +1 @@ -{{#isCookieParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}} @CookieValue(name = "{{baseName}}"{{^required}}, required = false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{>dateTimeParam}} {{>optionalDataType}} {{paramName}}{{/isCookieParam}} \ No newline at end of file +{{#isCookieParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}} @CookieValue(name = "{{baseName}}"{{^required}}, required = false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{>dateTimeParam}} {{>nullableAnnotation}}{{>optionalDataType}} {{paramName}}{{/isCookieParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/enumClass.mustache b/boat-scaffold/src/main/templates/boat-spring/enumClass.mustache index 0c8de0641..7fe64d41a 100644 --- a/boat-scaffold/src/main/templates/boat-spring/enumClass.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/enumClass.mustache @@ -1,10 +1,15 @@ /** * {{^description}}Gets or Sets {{{name}}}{{/description}}{{{description}}} */ - {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} { + {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#gson}} {{#allowableValues}} {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} @SerializedName({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}} @@ -14,6 +19,11 @@ {{^gson}} {{#allowableValues}} {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} {{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}} {{/enumVars}} @@ -41,10 +51,10 @@ @JsonCreator 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)) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { return b; } } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/isNullable}} + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} } - } \ No newline at end of file + } diff --git a/boat-scaffold/src/main/templates/boat-spring/enumOuterClass.mustache b/boat-scaffold/src/main/templates/boat-spring/enumOuterClass.mustache index 5528f1cef..436a12ddb 100644 --- a/boat-scaffold/src/main/templates/boat-spring/enumOuterClass.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/enumOuterClass.mustache @@ -7,16 +7,28 @@ import com.fasterxml.jackson.annotation.JsonValue; * {{^description}}Gets or Sets {{{name}}}{{/description}}{{{description}}} */ {{>additionalEnumTypeAnnotations}} + {{>generatedAnnotation}} -public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { + +public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#gson}} {{#allowableValues}}{{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} @SerializedName({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) {{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} {{/gson}} {{^gson}} {{#allowableValues}}{{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} {{{name}}}({{{value}}}){{^-last}}, {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} {{/gson}} @@ -42,10 +54,10 @@ public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatyp @JsonCreator 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)) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { return b; } } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/isNullable}} + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/formParams.mustache b/boat-scaffold/src/main/templates/boat-spring/formParams.mustache index 505e5909e..eaa958fc4 100644 --- a/boat-scaffold/src/main/templates/boat-spring/formParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/formParams.mustache @@ -1 +1 @@ -{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#isMultipart}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{>dateTimeParam}} {{{dataType}}} {{paramName}}{{/isMultipart}}{{^isMultipart}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} @RequestParam(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{{dataType}}} {{paramName}}{{/isMultipart}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#isArray}}List<{{/isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}} \ No newline at end of file +{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} {{>beanValidationBodyParams}}@Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{{dataType}}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isFile}}{{/isFormParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/generatedAnnotation.mustache b/boat-scaffold/src/main/templates/boat-spring/generatedAnnotation.mustache index 2f8ef3059..825f846fe 100644 --- a/boat-scaffold/src/main/templates/boat-spring/generatedAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/generatedAnnotation.mustache @@ -1 +1 @@ -@Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file +@Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}, comments = "Generator version: {{generatorVersion}}") \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/headerParams.mustache b/boat-scaffold/src/main/templates/boat-spring/headerParams.mustache index b9589c147..80b1d0a82 100644 --- a/boat-scaffold/src/main/templates/boat-spring/headerParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/headerParams.mustache @@ -1 +1 @@ -{{#isHeaderParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}} @RequestHeader(value = "{{baseName}}", required = {{#required}}true{{/required}}{{^required}}false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{>dateTimeParam}} {{>optionalDataType}} {{paramName}}{{/isHeaderParam}} \ No newline at end of file +{{#isHeaderParam}}{{#vendorExtensions.x-field-extra-annotation}}{{{.}}} {{/vendorExtensions.x-field-extra-annotation}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}} @RequestHeader(value = "{{baseName}}", required = {{#required}}true{{/required}}{{^required}}false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{>dateTimeParam}} {{>nullableAnnotation}}{{>optionalDataType}} {{paramName}}{{/isHeaderParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/homeController.mustache b/boat-scaffold/src/main/templates/boat-spring/homeController.mustache index 258392223..b54cff885 100644 --- a/boat-scaffold/src/main/templates/boat-spring/homeController.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/homeController.mustache @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; {{#sourceDocumentationProvider}} -import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import {{jacksonPackage}}.dataformat.yaml.YAMLMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.util.StreamUtils; diff --git a/boat-scaffold/src/main/templates/boat-spring/javaBuilder.mustache b/boat-scaffold/src/main/templates/boat-spring/javaBuilder.mustache new file mode 100644 index 000000000..6814c2983 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/javaBuilder.mustache @@ -0,0 +1,93 @@ + + public static class Builder {{#parentModel}}extends {{classname}}.Builder {{/parentModel}}{ + + private {{classname}} instance; + + public Builder() { + this(new {{classname}}()); + } + + protected Builder({{classname}} instance) { + {{#parentModel}} + super(instance); // the parent builder shares the same instance + {{/parentModel}} + this.instance = instance; + } + + protected Builder copyOf({{classname}} value) { {{#parentModel}} + super.copyOf(value);{{/parentModel}}{{#vars}} + this.instance.{{setter}}(value.{{name}});{{/vars}} + return this; + } + +{{#vars}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) { + this.instance.{{name}}({{name}}); + return this; + } + {{#openApiNullable}}{{#isNullable}} + public {{classname}}.Builder {{name}}(JsonNullable<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}> {{name}}) { + this.instance.{{name}} = {{name}}; + return this; + } + {{/isNullable}}{{/openApiNullable}} +{{/vars}} +{{#parentVars}} + @Override + public {{classname}}.Builder {{name}}({{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}} {{name}}) { + this.instance.{{name}}({{name}}); + return this; + } + {{#openApiNullable}}{{#isNullable}} + public {{classname}}.Builder {{name}}(JsonNullable<{{#removeAnnotations}}{{{datatypeWithEnum}}}{{/removeAnnotations}}> {{name}}) { + this.instance.{{setter}}({{name}}); + return this; + } + {{/isNullable}}{{/openApiNullable}} +{{/parentVars}} +{{#additionalPropertiesType}} + public {{classname}}.Builder additionalProperties(Map additionalProperties) { + this.instance.additionalProperties = additionalProperties; + return this; + } + +{{/additionalPropertiesType}} + /** + * returns a built {{classname}} instance. + * + * The builder is not reusable (NullPointerException) + */ + public {{classname}} build() { + try { + return this.instance; + } finally { + // ensure that this.instance is not reused{{#parentModel}} + super.build();{{/parentModel}} + this.instance = null; + } + } + + @Override + public String toString() { + return getClass() + "=(" + instance + ")"; + } + } + + /** + * Create a builder with no initialized field (except for the default values). + */ + public static {{classname}}.Builder builder() { + return new {{classname}}.Builder(); + } + + /** + * Create a builder with a shallow copy of this instance. + */ + public {{classname}}.Builder toBuilder() { + {{! using the instance setter ensure compatibility with Optional and JsonNullable}} + {{classname}}.Builder builder = new {{classname}}.Builder(); + return builder.copyOf(this); + } diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/RFC3339DateFormat.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/RFC3339DateFormat.mustache index b1a5cb59e..f1028f237 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/RFC3339DateFormat.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/RFC3339DateFormat.mustache @@ -1,6 +1,6 @@ package {{basePackage}}; -import com.fasterxml.jackson.databind.util.StdDateFormat; +import {{jacksonPackage}}.databind.util.StdDateFormat; import java.text.DateFormat; import java.text.FieldPosition; diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/application.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/application.mustache index f8740df95..e38e60151 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/application.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/application.mustache @@ -1,6 +1,11 @@ server.port={{serverPort}} spring.jackson.date-format={{basePackage}}.RFC3339DateFormat +{{#useSpringBoot4}} +spring.jackson.datatype.datetime.WRITE_DATES_AS_TIMESTAMPS=false +{{/useSpringBoot4}} +{{^useSpringBoot4}} spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false +{{/useSpringBoot4}} {{#virtualService}} virtualan.datasource.driver-class-name=org.hsqldb.jdbcDriver virtualan.datasource.jdbcurl=jdbc:hsqldb:mem:dataSource diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/openapi2SpringBoot.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/openapi2SpringBoot.mustache index 01725be2d..4f69d937e 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/openapi2SpringBoot.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/openapi2SpringBoot.mustache @@ -8,9 +8,16 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator; -@SpringBootApplication -@ComponentScan(basePackages = {"{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}) +@SpringBootApplication( + nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class +) +@ComponentScan( + basePackages = {"{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}, + nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class +) public class OpenApiGeneratorApplication { public static void main(String[] args) { @@ -18,7 +25,7 @@ public class OpenApiGeneratorApplication { } {{#openApiNullable}} - @Bean + @Bean(name = "{{basePackage}}.OpenApiGeneratorApplication.jsonNullableModule") public Module jsonNullableModule() { return new JsonNullableModule(); } diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb3.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb3.mustache index b2688d4c8..747c9272c 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb3.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb3.mustache @@ -7,22 +7,21 @@ {{artifactVersion}} 17 - ${java.version} - ${java.version} + ${java.version} UTF-8 {{#springDocDocumentationProvider}} - 2.0.2 + 2.6.0 {{/springDocDocumentationProvider}} {{^springDocDocumentationProvider}} {{#swagger2AnnotationLibrary}} - }2.2.7 + 2.2.42 {{/swagger2AnnotationLibrary}} {{/springDocDocumentationProvider}} {{#useSwaggerUI}} - 4.15.5 + 5.17.14 {{/useSwaggerUI}} {{#virtualService}} - 2.5.2 + 2.5.5 {{/virtualService}} {{#parentOverridden}} @@ -36,38 +35,47 @@ org.springframework.boot spring-boot-starter-parent - 3.0.2 + 3.3.13 {{/parentOverridden}} - - - repository.spring.milestone - Spring Milestone Repository - https://repo.spring.io/milestone - - - - - spring-milestones - https://repo.spring.io/milestone - - - src/main/java - {{^interfaceOnly}} + {{^interfaceOnly}} org.springframework.boot spring-boot-maven-plugin - {{#classifier}} + {{#classifier}} {{{classifier}}} + {{/classifier}} + {{#lombok}} + + + org.projectlombok + lombok + + + {{/lombok}} - {{/classifier}} + {{/interfaceOnly}} + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + {{/interfaceOnly}} {{#apiFirst}} org.openapitools @@ -101,7 +109,6 @@ {{/apiFirst}} - {{/interfaceOnly}} @@ -157,10 +164,6 @@ jsr305 3.0.2 - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - {{#withXml}} @@ -186,7 +189,7 @@ org.openapitools jackson-databind-nullable - 0.2.2 + 0.2.9 {{/openApiNullable}} {{#useBeanValidation}} @@ -217,6 +220,13 @@ spring-boot-starter-hateoas {{/hateoas}} +{{#lombok}} + + org.projectlombok + lombok + true + +{{/lombok}} com.fasterxml.jackson.core jackson-databind @@ -226,13 +236,5 @@ spring-boot-starter-test test -{{#useLombokAnnotations}} - - org.projectlombok - lombok - 1.18.30 - provided - -{{/useLombokAnnotations}} diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb4.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb4.mustache new file mode 100644 index 000000000..fc288bbb3 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom-sb4.mustache @@ -0,0 +1,251 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + + 17 + ${java.version} + UTF-8 + {{#springDocDocumentationProvider}} + 2.6.0 + {{/springDocDocumentationProvider}} + {{^springDocDocumentationProvider}} + {{#swagger2AnnotationLibrary}} + 2.2.42 + {{/swagger2AnnotationLibrary}} + {{/springDocDocumentationProvider}} + {{#useSwaggerUI}} + 5.17.14 + {{/useSwaggerUI}} + {{#virtualService}} + 2.5.5 + {{/virtualService}} + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} +{{^parentOverridden}} + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + +{{/parentOverridden}} + + + src/main/java + + {{^interfaceOnly}} + + org.springframework.boot + spring-boot-maven-plugin + + {{#classifier}} + {{{classifier}}} + {{/classifier}} + {{#lombok}} + + + org.projectlombok + lombok + + + {{/lombok}} + + + {{/interfaceOnly}} + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + {{/interfaceOnly}} + {{#apiFirst}} + + org.openapitools + openapi-generator-maven-plugin + {{{generatorVersion}}} + + + + generate + + + src/main/resources/openapi.yaml + spring + {{{apiPackage}}} + {{{modelPackage}}} + false + {{#modelNamePrefix}} + {{{.}}} + {{/modelNamePrefix}} + {{#modelNameSuffix}} + {{{.}}} + {{/modelNameSuffix}} + + {{#configOptions}} + <{{left}}>{{right}} + {{/configOptions}} + + + + + + {{/apiFirst}} + + + + + org.springframework.boot + spring-boot-starter-web{{#reactive}}flux{{/reactive}}{{^reactive}}mvc{{/reactive}} + + + org.springframework.boot + spring-boot-starter-{{#reactive}}web{{/reactive}}{{^reactive}}rest{{/reactive}}client + + + org.springframework.data + spring-data-commons + + {{#springDocDocumentationProvider}} + + {{#useSwaggerUI}} + + org.springdoc + springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui + ${springdoc.version} + + {{/useSwaggerUI}} + {{^useSwaggerUI}} + + org.springdoc + springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-api + ${springdoc.version} + + {{/useSwaggerUI}} + {{/springDocDocumentationProvider}} + {{#useSwaggerUI}} + {{^springDocDocumentationProvider}} + + org.webjars + swagger-ui + ${swagger-ui.version} + + + org.webjars + webjars-locator-core + + {{/springDocDocumentationProvider}} + {{/useSwaggerUI}} + {{^springDocDocumentationProvider}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations.version} + + {{/swagger2AnnotationLibrary}} + {{/springDocDocumentationProvider}} + + + com.google.code.findbugs + jsr305 + 3.0.2 + + {{#withXml}} + + + jakarta.xml.bind + jakarta.xml.bind-api + + + {{jacksonPackage}}.dataformat + jackson-dataformat-xml + + {{/withXml}} + {{^useJackson3}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + {{/useJackson3}} + {{#joda}} + + {{jacksonPackage}}.datatype + jackson-datatype-joda + + {{/joda}} + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + 0.2.8 + + {{/openApiNullable}} +{{#useBeanValidation}} + + + org.springframework.boot + spring-boot-starter-validation + +{{/useBeanValidation}} +{{#virtualService}} + + + io.virtualan + virtualan-plugin + ${virtualan.version} + + + + org.hsqldb + hsqldb + + +{{/virtualService}} +{{#hateoas}} + + + org.springframework.boot + spring-boot-starter-hateoas + +{{/hateoas}} +{{#lombok}} + + org.projectlombok + lombok + true + +{{/lombok}} + + {{jacksonPackage}}.core + jackson-databind + + + org.springframework.boot + spring-boot-starter-web{{#reactive}}flux{{/reactive}}{{^reactive}}mvc{{/reactive}}-test + test + + + org.springframework.boot + spring-boot-starter-{{#reactive}}web{{/reactive}}{{^reactive}}rest{{/reactive}}client + test + + + diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom.mustache index 9e792f14e..38c29f364 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-boot/pom.mustache @@ -6,7 +6,12 @@ {{artifactId}} {{artifactVersion}} + {{#useSealed}} + 17 + {{/useSealed}} + {{^useSealed}} 1.8 + {{/useSealed}} ${java.version} ${java.version} UTF-8 @@ -22,7 +27,7 @@ 1.6.6 {{/swagger1AnnotationLibrary}} {{#swagger2AnnotationLibrary}} - }2.2.7 + 2.2.7 {{/swagger2AnnotationLibrary}} {{/springDocDocumentationProvider}} {{/springFoxDocumentationProvider}} @@ -30,7 +35,7 @@ 2.5.2 {{/virtualService}} {{#useSwaggerUI}} - 4.15.5 + 5.3.1 {{/useSwaggerUI}} {{#parentOverridden}} @@ -44,23 +49,46 @@ org.springframework.boot spring-boot-starter-parent - {{#springFoxDocumentationProvider}}2.5.14{{/springFoxDocumentationProvider}}{{^springFoxDocumentationProvider}}2.7.6{{/springFoxDocumentationProvider}} + {{#springFoxDocumentationProvider}}2.5.14{{/springFoxDocumentationProvider}}{{^springFoxDocumentationProvider}}2.7.15{{/springFoxDocumentationProvider}} {{/parentOverridden}} src/main/java - {{^interfaceOnly}} + {{^interfaceOnly}} org.springframework.boot spring-boot-maven-plugin - {{#classifier}} + {{#classifier}} {{{classifier}}} - {{/classifier}} + {{#lombok}} + + + org.projectlombok + lombok + + + {{/lombok}} + + + {{/interfaceOnly}} + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + {{/interfaceOnly}} {{#apiFirst}} org.openapitools @@ -94,50 +122,8 @@ {{/apiFirst}} - {{/interfaceOnly}} - {{#useJakartaEe}} - - jakarta.persistence - jakarta.persistence-api - 3.1.0 - - - jakarta.servlet - jakarta.servlet-api - 6.0.0 - - - jakarta.annotation - jakarta.annotation-api - 2.1.1 - - - jakarta.validation - jakarta.validation-api - 3.0.2 - - {{/useJakartaEe}} - {{^useJakartaEe}} - - javax.persistence - javax.persistence-api - 2.2 - - - javax.servlet - javax.servlet-api - 4.0.1 - - {{/useJakartaEe}} - {{#useLombokAnnotations}} - - org.projectlombok - lombok - 1.18.30 - - {{/useLombokAnnotations}} org.springframework.boot spring-boot-starter-web{{#reactive}}flux{{/reactive}} @@ -208,10 +194,6 @@ jsr305 3.0.2 - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - {{#withXml}} @@ -237,7 +219,7 @@ org.openapitools jackson-databind-nullable - 0.2.2 + 0.2.8 {{/openApiNullable}} {{#useBeanValidation}} @@ -268,6 +250,13 @@ spring-boot-starter-hateoas {{/hateoas}} +{{#lombok}} + + org.projectlombok + lombok + true + +{{/lombok}} com.fasterxml.jackson.core jackson-databind diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/apiClient.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/apiClient.mustache index adc5145d0..1aacfd064 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/apiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/apiClient.mustache @@ -3,6 +3,6 @@ package {{package}}; import org.springframework.cloud.openfeign.FeignClient; import {{configPackage}}.ClientConfiguration; -@FeignClient(name="${{openbrace}}{{classVarName}}.name:{{classVarName}}{{closebrace}}", {{#useFeignClientUrl}}url="${{openbrace}}{{classVarName}}.url:{{basePath}}{{closebrace}}", {{/useFeignClientUrl}}configuration = ClientConfiguration.class) +@FeignClient(name="${{openbrace}}{{classVarName}}.name:{{classVarName}}{{closebrace}}", {{#useFeignClientContextId}}contextId="${{openbrace}}{{classVarName}}.contextId:${{openbrace}}{{classVarName}}.name{{closebrace}}{{closebrace}}",{{/useFeignClientContextId}} {{#useFeignClientUrl}}url="${{openbrace}}{{classVarName}}.url:{{basePath}}{{closebrace}}",{{/useFeignClientUrl}} configuration = ClientConfiguration.class) public interface {{classname}}Client extends {{classname}} { } diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientConfiguration.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientConfiguration.mustache index 1e9a0940b..24968ff84 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientConfiguration.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientConfiguration.mustache @@ -1,50 +1,44 @@ package {{configPackage}}; {{#authMethods}} -{{#isBasic}} +{{#isBasicBasic}} import feign.auth.BasicAuthRequestInterceptor; -{{/isBasic}} -{{#-first}} +{{^hasApiKeyMethods}} import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -{{/-first}} -{{#isOAuth}} -import org.springframework.boot.context.properties.ConfigurationProperties; -{{/isOAuth}} +{{/hasApiKeyMethods}} +{{/isBasicBasic}} {{/authMethods}} -import org.springframework.boot.context.properties.EnableConfigurationProperties; -{{#authMethods}} -{{#-first}} +{{#hasAuthMethods}} import org.springframework.context.annotation.Bean; -{{/-first}} -{{/authMethods}} +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +{{#hasApiKeyMethods}} +import org.springframework.beans.factory.annotation.Value; +{{/hasApiKeyMethods}} +{{#hasOAuthMethods}} +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.http.HttpHeaders; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +{{/hasOAuthMethods}} +{{/hasAuthMethods}} import org.springframework.context.annotation.Configuration; -{{#authMethods}} -{{#isOAuth}} -import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor; -import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -{{#isApplication}} -import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -{{/isApplication}} -{{#isCode}} -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -{{/isCode}} -{{#isImplicit}} -import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails; -{{/isImplicit}} -{{#isPassword}} -import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; -{{/isPassword}} -{{/isOAuth}} -{{/authMethods}} -@Configuration -@EnableConfigurationProperties + public class ClientConfiguration { {{#authMethods}} - {{#isBasic}} + {{#isBasicBasic}} @Value("${{openbrace}}{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.username:{{closebrace}}") private String {{{name}}}Username; @@ -57,7 +51,7 @@ public class ClientConfiguration { return new BasicAuthRequestInterceptor(this.{{{name}}}Username, this.{{{name}}}Password); } - {{/isBasic}} + {{/isBasicBasic}} {{#isApiKey}} @Value("${{openbrace}}{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.key:{{closebrace}}") private String {{{name}}}Key; @@ -70,63 +64,53 @@ public class ClientConfiguration { {{/isApiKey}} {{#isOAuth}} - @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - public OAuth2FeignRequestInterceptor {{{name}}}RequestInterceptor(OAuth2ClientContext oAuth2ClientContext) { - return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, {{{name}}}ResourceDetails()); - } + private static final String CLIENT_PRINCIPAL_{{#lambda.uppercase}}{{{flow}}}{{/lambda.uppercase}} = "oauth2FeignClient"; @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - public OAuth2ClientContext oAuth2ClientContext() { - return new DefaultOAuth2ClientContext(); + @ConditionalOnProperty( prefix = "spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}", name = "enabled", havingValue = "true" ) + public OAuth2RequestInterceptor {{{flow}}}OAuth2RequestInterceptor(final OAuth2AuthorizedClientManager {{{flow}}}AuthorizedClientManager ) { + return new OAuth2RequestInterceptor(OAuth2AuthorizeRequest.withClientRegistrationId("{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}") + .principal( new AnonymousAuthenticationToken( CLIENT_PRINCIPAL_{{#lambda.uppercase}}{{{flow}}}{{/lambda.uppercase}}, CLIENT_PRINCIPAL_{{#lambda.uppercase}}{{{flow}}}{{/lambda.uppercase}}, AuthorityUtils.createAuthorityList( "ROLE_ANONYMOUS" ) ) ) + .build(), {{{flow}}}AuthorizedClientManager ); } - {{#isCode}} @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - @ConfigurationProperties("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}") - public AuthorizationCodeResourceDetails {{{name}}}ResourceDetails() { - AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); - details.setAccessTokenUri("{{{tokenUrl}}}"); - details.setUserAuthorizationUri("{{{authorizationUrl}}}"); - return details; + @ConditionalOnProperty( prefix = "spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}", name = "enabled", havingValue = "true" ) + public OAuth2AuthorizedClientManager {{{flow}}}AuthorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService authorizedClientService ) { + return new AuthorizedClientServiceOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService ); } + {{/isOAuth}} +{{/authMethods}} +{{#hasOAuthMethods}} + public static class OAuth2RequestInterceptor implements RequestInterceptor { - {{/isCode}} - {{#isPassword}} - @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - @ConfigurationProperties("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}") - public ResourceOwnerPasswordResourceDetails {{{name}}}ResourceDetails() { - ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); - details.setAccessTokenUri("{{{tokenUrl}}}"); - return details; - } + private final OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager; + private final OAuth2AuthorizeRequest oAuth2AuthorizeRequest; - {{/isPassword}} - {{#isApplication}} - @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - @ConfigurationProperties("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}") - public ClientCredentialsResourceDetails {{{name}}}ResourceDetails() { - ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); - details.setAccessTokenUri("{{{tokenUrl}}}"); - return details; - } + public OAuth2RequestInterceptor(OAuth2AuthorizeRequest oAuth2AuthorizeRequest,OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager){ + this.oAuth2AuthorizeRequest = oAuth2AuthorizeRequest; + this.oAuth2AuthorizedClientManager = oAuth2AuthorizedClientManager; + } - {{/isApplication}} - {{#isImplicit}} - @Bean - @ConditionalOnProperty("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}.client-id") - @ConfigurationProperties("{{#lambda.lowercase}}{{{title}}}{{/lambda.lowercase}}.security.{{{name}}}") - public ImplicitResourceDetails {{{name}}}ResourceDetails() { - ImplicitResourceDetails details = new ImplicitResourceDetails(); - details.setUserAuthorizationUri("{{{authorizationUrl}}}"); - return details; + @Override + public void apply( final RequestTemplate template ) { + template.header( HttpHeaders.AUTHORIZATION, getBearerToken() ); + } + + public OAuth2AccessToken getAccessToken() { + final OAuth2AuthorizedClient authorizedClient = oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest); + if (authorizedClient == null) { + throw new OAuth2AuthenticationException( "Client failed to authenticate"); + } + return authorizedClient.getAccessToken(); + } + + public String getBearerToken() { + final OAuth2AccessToken accessToken = getAccessToken(); + return String.format(java.util.Locale.ROOT, "%s %s", accessToken.getTokenType().getValue(), accessToken.getTokenValue() ); + } } +{{/hasOAuthMethods}} - {{/isImplicit}} - {{/isOAuth}} -{{/authMethods}} } diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientPropertiesConfiguration.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientPropertiesConfiguration.mustache new file mode 100644 index 000000000..cb855cb5c --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/clientPropertiesConfiguration.mustache @@ -0,0 +1,43 @@ +package {{configPackage}}; + + +import java.util.Properties; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; + +@Configuration(value = "{{configPackage}}.clientPropertiesConfiguration") +public class ClientPropertiesConfiguration { + + public ClientPropertiesConfiguration( final ConfigurableEnvironment configurableEnvironment ) { +{{#authMethods}} + {{#isOAuth}} + final Properties {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}} = new Properties(); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.client-id", "set-{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}-client-id" ); + {{#hasScopes}} + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("{{#scopes}}{{#-first}}spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.scope", "{{/-first}}{{scope}}{{^-last}},{{/-last}}{{/scopes}}" ); + {{/hasScopes}} + {{#isCode}} + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-grant-type", "authorization_code" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.redirect-uri", "set-{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}-redirect-uri" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.provider.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.token-uri", "{{{tokenUrl}}}" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.provider.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-uri", "{{{authorizationUrl}}}" ); + {{/isCode}} + {{#isPassword}} + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-grant-type", "password" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.provider.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.token-uri", "{{{tokenUrl}}}" ); + {{/isPassword}} + {{#isApplication}} + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-grant-type", "client_credentials" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.provider.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.token-uri", "{{{tokenUrl}}}" ); + {{/isApplication}} + {{#isImplicit}} + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.registration.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-grant-type", "implicit" ); + {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.put("spring.security.oauth2.client.provider.{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}.authorization-uri", "{{{authorizationUrl}}}" ); + {{/isImplicit}} + configurableEnvironment.getPropertySources().addLast( new PropertiesPropertySource("{{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}}", {{{name}}}{{#lambda.pascalcase}}{{{flow}}}{{/lambda.pascalcase}} ) ); + {{/isOAuth}} +{{/authMethods}} + } + +} diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb3.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb3.mustache index f61faca01..d62bb06bf 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb3.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb3.mustache @@ -6,16 +6,15 @@ {{artifactId}} {{artifactVersion}} - 1.8 - ${java.version} - ${java.version} + 17 + ${java.version} UTF-8 {{#springDocDocumentationProvider}} - 2.0.0-M3 + 2.2.0 {{/springDocDocumentationProvider}} {{^springDocDocumentationProvider}} {{#swagger2AnnotationLibrary}} - }2.2.0 + 2.2.15 {{/swagger2AnnotationLibrary}} {{/springDocDocumentationProvider}} @@ -30,21 +29,27 @@ org.springframework.boot spring-boot-starter-parent - 3.0.0-M3 + 3.3.13 {{/parentOverridden}} - - - repository.spring.milestone - Spring Milestone Repository - https://repo.spring.io/milestone - - - src/main/java + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + {{/interfaceOnly}} {{^parentOverridden}} @@ -53,7 +58,7 @@ org.springframework.cloud spring-cloud-starter-parent - 2022.0.0-M2 + 2023.0.0 pom import @@ -92,11 +97,8 @@ spring-cloud-starter-openfeign - org.springframework.cloud - spring-cloud-starter-oauth2 - {{^parentOverridden}} - 2.2.5.RELEASE - {{/parentOverridden}} + org.springframework.security + spring-security-oauth2-client {{#withXml}} @@ -120,7 +122,7 @@ org.openapitools jackson-databind-nullable {{^parentOverridden}} - 0.2.2 + 0.2.9 {{/parentOverridden}} {{/openApiNullable}} @@ -130,6 +132,13 @@ spring-boot-starter-hateoas {{/hateoas}} + {{#lombok}} + + org.projectlombok + lombok + true + + {{/lombok}} {{#useBeanValidation}} org.springframework.boot diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb4.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb4.mustache new file mode 100644 index 000000000..849ed1f26 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom-sb4.mustache @@ -0,0 +1,167 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + + 17 + ${java.version} + UTF-8 + {{#springDocDocumentationProvider}} + 2.2.0 + {{/springDocDocumentationProvider}} + {{^springDocDocumentationProvider}} + {{#swagger2AnnotationLibrary}} + 2.2.15 + {{/swagger2AnnotationLibrary}} + {{/springDocDocumentationProvider}} + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} +{{^parentOverridden}} + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + +{{/parentOverridden}} + + + src/main/java + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + {{/interfaceOnly}} + + +{{^parentOverridden}} + + + + org.springframework.cloud + spring-cloud-dependencies + 2025.1.0 + pom + import + + + + +{{/parentOverridden}} + + {{#springDocDocumentationProvider}} + + + org.springdoc + springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui + ${springdoc.version} + + {{/springDocDocumentationProvider}} + {{^springDocDocumentationProvider}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations.version} + + {{/swagger2AnnotationLibrary}} + {{/springDocDocumentationProvider}} + + + com.google.code.findbugs + jsr305 + {{^parentOverridden}} + 3.0.2 + {{/parentOverridden}} + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.security + spring-security-oauth2-client + {{^parentOverridden}} + 6.1.1 + {{/parentOverridden}} + + + {{jacksonPackage}}.core + jackson-databind + + {{#withXml}} + + + {{jacksonPackage}}.dataformat + jackson-dataformat-xml + + {{/withXml}} + {{^useJackson3}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + {{/useJackson3}} + {{#joda}} + + {{jacksonPackage}}.datatype + jackson-datatype-joda + + {{/joda}} + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + {{^parentOverridden}} + 0.2.8 + {{/parentOverridden}} + + {{/openApiNullable}} + {{#hateoas}} + + org.springframework.boot + spring-boot-starter-hateoas + + {{/hateoas}} + {{#lombok}} + + org.projectlombok + lombok + true + + {{/lombok}} + {{#useBeanValidation}} + + org.springframework.boot + spring-boot-starter-validation + + {{/useBeanValidation}} + + org.springframework.data + spring-data-commons + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom.mustache index 0510c4153..060c2e6ef 100644 --- a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-cloud/pom.mustache @@ -6,7 +6,12 @@ {{artifactId}} {{artifactVersion}} + {{#useSealed}} + 17 + {{/useSealed}} + {{^useSealed}} 1.8 + {{/useSealed}} ${java.version} ${java.version} UTF-8 @@ -14,7 +19,7 @@ 2.9.2 {{/springFoxDocumentationProvider}} {{#springDocDocumentationProvider}} - 1.6.8 + 1.6.14 {{/springDocDocumentationProvider}} {{^springFoxDocumentationProvider}} {{^springDocDocumentationProvider}} @@ -22,7 +27,7 @@ 1.6.6 {{/swagger1AnnotationLibrary}} {{#swagger2AnnotationLibrary}} - }2.1.13 + 2.2.7 {{/swagger2AnnotationLibrary}} {{/springDocDocumentationProvider}} {{/springFoxDocumentationProvider}} @@ -38,12 +43,26 @@ org.springframework.boot spring-boot-starter-parent - 2.7.0 + 2.7.15 {{/parentOverridden}} src/main/java + {{#interfaceOnly}} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + {{/interfaceOnly}} {{^parentOverridden}} @@ -52,7 +71,7 @@ org.springframework.cloud spring-cloud-starter-parent - 2021.0.1 + 2021.0.5 pom import @@ -108,10 +127,10 @@ spring-cloud-starter-openfeign - org.springframework.cloud - spring-cloud-starter-oauth2 + org.springframework.security + spring-security-oauth2-client {{^parentOverridden}} - 2.2.5.RELEASE + 5.7.8 {{/parentOverridden}} {{#withXml}} @@ -136,7 +155,7 @@ org.openapitools jackson-databind-nullable {{^parentOverridden}} - 0.2.2 + 0.2.8 {{/parentOverridden}} {{/openApiNullable}} @@ -146,6 +165,13 @@ spring-boot-starter-hateoas {{/hateoas}} + {{#lombok}} + + org.projectlombok + lombok + true + + {{/lombok}} {{#useBeanValidation}} org.springframework.boot diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/README.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/README.mustache new file mode 100644 index 000000000..f493d21d4 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/README.mustache @@ -0,0 +1,21 @@ +# OpenAPI generated API stub + +[Spring Framework 6.1 HTTP Interface](https://docs.spring.io/spring-framework/docs/6.1.0/reference/html/integration.html#rest-http-interface) + + +## Overview +This code was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. +By using the [OpenAPI-Spec](https://openapis.org), you can easily generate an API stub. +This is an example of building API stub interfaces in Java using the Spring framework. + +The stubs generated can be used in your existing Spring application for HTTP integration with other REST services +To use auto-generated interfaces you have to create your own configuration which extends default abstract configurator & provide `{{#reactive}}WebClient{{/reactive}}{{^reactive}}RestClient{{/reactive}}` instance via constructor +```java +@Configuration +public class MyConfiguration extends {{configPackage}}.HttpInterfacesAbstractConfigurator { + + public MyConfiguration({{#reactive}}WebClient{{/reactive}}{{^reactive}}RestClient{{/reactive}} client) { + super(client); + } +} +``` \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/api.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/api.mustache new file mode 100644 index 000000000..debda4a3c --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/api.mustache @@ -0,0 +1,76 @@ +/* + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) ({{{generatorVersion}}}). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +{{^useResponseEntity}} +import org.springframework.http.HttpStatus; +{{/useResponseEntity}} +{{#useResponseEntity}} +import org.springframework.http.ResponseEntity; +{{/useResponseEntity}} +import org.springframework.web.bind.annotation.*; +import org.springframework.web.service.annotation.*; +import org.springframework.web.multipart.MultipartFile; +{{#reactive}} + +import org.springframework.http.codec.multipart.Part; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +{{/reactive}} + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import {{javaxPackage}}.annotation.Generated; + + +{{>generatedAnnotation}} + +{{#operations}} +public interface {{classname}} { +{{#operation}} + + /** + * {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}} + {{#notes}} + * {{.}} + {{/notes}} + * + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} + {{/allParams}} + * @return {{#responses}}{{message}} (status code {{code}}){{^-last}} + * or {{/-last}}{{/responses}} + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + {{^useResponseEntity}} + @ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}}) + {{/useResponseEntity}} + @HttpExchange( + method = "{{{httpMethod}}}", + value = "{{{path}}}", + accept = { {{#vendorExtensions.x-accepts}}"{{{.}}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-accepts}} }{{#vendorExtensions.x-content-type}}, + contentType = "{{{vendorExtensions.x-content-type}}}"{{/vendorExtensions.x-content-type}} + ) + {{>responseType}} {{operationId}}( + {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, + {{/-last}}{{/allParams}} + ){{#unhandledException}} throws Exception{{/unhandledException}}; + +{{/operation}} +} +{{/operations}} diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/httpInterfacesConfiguration.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/httpInterfacesConfiguration.mustache new file mode 100644 index 000000000..d6d1855f2 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/httpInterfacesConfiguration.mustache @@ -0,0 +1,49 @@ +/* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) ({{{generatorVersion}}}). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package {{configPackage}}; + +{{#apiInfo}} + {{#apis}} +import {{apiPackage}}.{{classname}}; + {{/apis}} +{{/apiInfo}} + +import org.springframework.context.annotation.Bean; +{{#reactive}} +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.support.WebClientAdapter; +{{/reactive}} +{{^reactive}} +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; +{{/reactive}} +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +public abstract class HttpInterfacesAbstractConfigurator { + + private final {{#reactive}}WebClient{{/reactive}}{{^reactive}}RestClient{{/reactive}} client; + + public HttpInterfacesAbstractConfigurator(final {{#reactive}}WebClient{{/reactive}}{{^reactive}}RestClient{{/reactive}} client) { + this.client = client; + } + +{{#apiInfo}} +{{#apis}} + @Bean(name = "{{configPackage}}.HttpInterfacesAbstractConfigurator.{{classVarName}}") + {{classname}} {{classVarName}}HttpProxy() { + {{#reactive}} + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build(); + {{/reactive}} + {{^reactive}} + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client)).build(); + {{/reactive}} + return factory.createClient({{classname}}.class); + } + +{{/apis}} +{{/apiInfo}} + +} diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom-sb4.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom-sb4.mustache new file mode 100644 index 000000000..e617a2289 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom-sb4.mustache @@ -0,0 +1,107 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + + 17 + UTF-8 + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} +{{^parentOverridden}} + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + +{{/parentOverridden}} + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + org.springframework.boot + spring-boot-starter-{{#reactive}}web{{/reactive}}{{^reactive}}rest{{/reactive}}client + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + jakarta.validation + jakarta.validation-api + + {{#withXml}} + + + jakarta.xml.bind + jakarta.xml.bind-api + + + {{jacksonPackage}}.dataformat + jackson-dataformat-xml + + {{/withXml}} + {{^useJackson3}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + {{/useJackson3}} + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + 0.2.8 + + {{/openApiNullable}} + {{#lombok}} + + org.projectlombok + lombok + true + + {{/lombok}} + + org.springframework.boot + spring-boot-starter-{{#reactive}}web{{/reactive}}{{^reactive}}rest{{/reactive}}client-test + test + + + diff --git a/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom.mustache b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom.mustache new file mode 100644 index 000000000..7a1317745 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/libraries/spring-http-interface/pom.mustache @@ -0,0 +1,105 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + + 17 + UTF-8 + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} +{{^parentOverridden}} + + org.springframework.boot + spring-boot-starter-parent + 3.3.13 + + +{{/parentOverridden}} + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + org.springframework.boot + spring-boot-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}web{{/reactive}} + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + jakarta.validation + jakarta.validation-api + + {{#withXml}} + + + jakarta.xml.bind + jakarta.xml.bind-api + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + {{/withXml}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + 0.2.9 + + {{/openApiNullable}} + {{#lombok}} + + org.projectlombok + lombok + true + + {{/lombok}} + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/boat-scaffold/src/main/templates/boat-spring/lombokAnnotation.mustache b/boat-scaffold/src/main/templates/boat-spring/lombokAnnotation.mustache new file mode 100644 index 000000000..50834e202 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/lombokAnnotation.mustache @@ -0,0 +1,39 @@ +{{#lombok.RequiredArgsConstructor}} + {{^useBeanValidation}} + {{#required}} + @lombok.NonNull + {{/required}} + {{/useBeanValidation}} +{{/lombok.RequiredArgsConstructor}} +{{#lombok.ToString}} + {{#isPassword}} + @lombok.ToString.Exclude + {{/isPassword}} +{{/lombok.ToString}} +{{#lombok.Data}} + {{#isPassword}} + @lombok.ToString.Exclude + {{/isPassword}} + {{^isContainer}}{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}}{{/isContainer}} + {{^useBeanValidation}} + {{#required}} + @lombok.NonNull + {{/required}} + {{/useBeanValidation}} + {{#swagger2AnnotationLibrary}} + @Schema(name = "{{{baseName}}}"{{#isReadOnly}}, accessMode = Schema.AccessMode.READ_ONLY{{/isReadOnly}}{{#example}}, example = "{{{.}}}"{{/example}}{{#description}}, description = "{{{.}}}"{{/description}}{{#deprecated}}, deprecated = true{{/deprecated}}, requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}) + {{/swagger2AnnotationLibrary}} + {{#jackson}}@JsonProperty("{{baseName}}") + {{#withXml}} + @JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isContainer}} + @JacksonXmlElementWrapper({{#isXmlWrapped}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}", {{#xmlNamespace}}namespace = "{{.}}", {{/xmlNamespace}}{{/isXmlWrapped}}useWrapping = {{isXmlWrapped}}) + {{/isContainer}} + {{/withXml}} + {{/jackson}} +{{/lombok.Data}} +{{#lombok.Builder}} + {{#defaultValue}} + @lombok.Builder.Default + {{/defaultValue}} +{{/lombok.Builder}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/methodBody.mustache b/boat-scaffold/src/main/templates/boat-spring/methodBody.mustache index a7ccd54c4..57045774d 100644 --- a/boat-scaffold/src/main/templates/boat-spring/methodBody.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/methodBody.mustache @@ -3,28 +3,37 @@ {{#-first}} {{#async}} return CompletableFuture.supplyAsync(()-> { - {{/async}}getRequest().ifPresent(request -> { + {{/async}}{{#returnSuccessCode}}AtomicInteger statusCode = new AtomicInteger(501); + {{/returnSuccessCode}}getRequest().ifPresent(request -> { {{#async}} {{/async}} for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { {{/-first}} {{#async}} {{/async}}{{^async}} {{/async}} if (mediaType.isCompatibleWith(MediaType.valueOf("{{{contentType}}}"))) { {{#async}} {{/async}}{{^async}} {{/async}} String exampleString = {{>exampleString}}; -{{#async}} {{/async}}{{^async}} {{/async}} ApiUtil.setExampleResponse(request, "{{{contentType}}}", exampleString); +{{#async}} {{/async}}{{^async}} {{/async}} ApiUtil.setExampleResponse(request, "{{{contentType}}}", exampleString);{{#returnSuccessCode}} +{{#async}} {{/async}}{{^async}} {{/async}} statusCode.set({{{statusCode}}});{{/returnSuccessCode}} {{#async}} {{/async}}{{^async}} {{/async}} break; {{#async}} {{/async}}{{^async}} {{/async}} } {{#-last}} {{#async}} {{/async}}{{^async}} {{/async}} } {{#async}} {{/async}} }); -{{#async}} {{/async}} return new ResponseEntity<>({{#returnSuccessCode}}HttpStatus.valueOf({{{statusCode}}}){{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); +{{#async}} {{/async}} {{#useResponseEntity}}return new ResponseEntity<>({{#returnSuccessCode}}HttpStatus.valueOf(statusCode.get()){{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); +{{/useResponseEntity}} +{{^useResponseEntity}}throw new IllegalArgumentException("Not implemented"); +{{/useResponseEntity}} {{#async}} }, Runnable::run); {{/async}} {{/-last}} {{/examples}} {{^examples}} -return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>({{#returnSuccessCode}}HttpStatus.OK{{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}){{#async}}){{/async}}; +{{#useResponseEntity}}return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>({{#returnSuccessCode}}HttpStatus.OK{{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}){{#async}}){{/async}}; +{{/useResponseEntity}} +{{^useResponseEntity}}throw new IllegalArgumentException("Not implemented"); +{{/useResponseEntity}} {{/examples}} {{/reactive}} {{#reactive}} +{{^vendorExtensions.x-sse}} Mono result = Mono.empty(); {{#examples}} {{#-first}} @@ -33,7 +42,7 @@ Mono result = Mono.empty(); {{/-first}} if (mediaType.isCompatibleWith(MediaType.valueOf("{{{contentType}}}"))) { String exampleString = {{>exampleString}}; - result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString); + result = ApiUtil.getExampleResponse(exchange, MediaType.valueOf("{{{generatedContentType}}}"), exampleString); break; } {{#-last}} @@ -43,5 +52,10 @@ Mono result = Mono.empty(); {{^examples}} exchange.getResponse().setStatusCode({{#returnSuccessCode}}HttpStatus.OK{{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); {{/examples}} - return result{{#allParams}}{{#isBodyParam}}{{^isArray}}{{#paramName}}.then({{.}}){{/paramName}}{{/isArray}}{{#isArray}}{{#paramName}}.thenMany({{.}}){{/paramName}}{{/isArray}}{{/isBodyParam}}{{/allParams}}.then(Mono.empty()); + return result{{#allParams}}{{#isBodyParam}}{{^isArray}}{{#paramName}}.then({{.}}){{/paramName}}{{/isArray}}{{#isArray}}{{#paramName}}.thenMany({{.}}){{/paramName}}{{/isArray}}{{/isBodyParam}}{{/allParams}}{{#isArray}}{{#useResponseEntity}}.then(Mono.empty()){{/useResponseEntity}}{{^useResponseEntity}}.thenMany(Flux.empty()){{/useResponseEntity}}{{/isArray}}{{^isArray}}.then(Mono.empty()){{/isArray}}; +{{/vendorExtensions.x-sse}} +{{#vendorExtensions.x-sse}} +exchange.getResponse().setStatusCode({{#returnSuccessCode}}HttpStatus.valueOf({{{statusCode}}}){{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); + return Flux.empty(); +{{/vendorExtensions.x-sse}} {{/reactive}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/model.mustache b/boat-scaffold/src/main/templates/boat-spring/model.mustache index 896d93104..7655d0701 100644 --- a/boat-scaffold/src/main/templates/boat-spring/model.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/model.mustache @@ -1,15 +1,3 @@ -/* -Boat Generator configuration: - useBeanValidation: {{useBeanValidation}} - useOptional: {{useOptional}} - addServletRequest: {{addServletRequest}} - useLombokAnnotations: {{useLombokAnnotations}} - openApiNullable: {{openApiNullable}} - useSetForUniqueItems: {{useSetForUniqueItems}} - useWithModifiers: {{useWithModifiers}} - useJakartaEe: {{useJakartaEe}} - useSpringBoot3: {{useSpringBoot3}} -*/ package {{package}}; import java.net.URI; @@ -24,30 +12,20 @@ import java.io.Serializable; {{/serializableModel}} import java.time.OffsetDateTime; {{#useBeanValidation}} -{{#useJakartaEe}} -import jakarta.validation.Valid; -import jakarta.validation.constraints.*; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.validation.Valid; -import javax.validation.constraints.*; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.Valid; +import {{javaxPackage}}.validation.constraints.*; {{/useBeanValidation}} {{^useBeanValidation}} -{{#useJakartaEe}} -import jakarta.validation.constraints.*; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.validation.constraints.*; -{{/useJakartaEe}} +import {{javaxPackage}}.validation.constraints.NotNull; {{/useBeanValidation}} {{#performBeanValidation}} import org.hibernate.validator.constraints.*; {{/performBeanValidation}} {{#jackson}} {{#withXml}} -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import {{jacksonPackage}}.dataformat.xml.annotation.JacksonXmlRootElement; +import {{jacksonPackage}}.dataformat.xml.annotation.JacksonXmlProperty; +import {{jacksonPackage}}.dataformat.xml.annotation.JacksonXmlElementWrapper; {{/withXml}} {{/jackson}} {{#swagger2AnnotationLibrary}} @@ -55,7 +33,7 @@ import io.swagger.v3.oas.annotations.media.Schema; {{/swagger2AnnotationLibrary}} {{#withXml}} -import javax.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.*; {{/withXml}} {{^parent}} {{#hateoas}} @@ -64,20 +42,22 @@ import org.springframework.hateoas.RepresentationModel; {{/parent}} import java.util.*; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; {{#models}} {{#model}} +{{#additionalPropertiesType}} +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +{{/additionalPropertiesType}} {{#isEnum}} {{>enumOuterClass}} + {{/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}} \ No newline at end of file +{{/models}} diff --git a/boat-scaffold/src/main/templates/boat-spring/notFoundException.mustache b/boat-scaffold/src/main/templates/boat-spring/notFoundException.mustache index 11ad8c965..3a5bb6824 100644 --- a/boat-scaffold/src/main/templates/boat-spring/notFoundException.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/notFoundException.mustache @@ -1,13 +1,9 @@ package {{apiPackage}}; -{{#useJakartaEe}} -import jakarta.annotation.Generated; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; {{>generatedAnnotation}} + public class NotFoundException extends ApiException { private int code; public NotFoundException (int code, String msg) { diff --git a/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache b/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache new file mode 100644 index 000000000..de4ee36cf --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache @@ -0,0 +1 @@ +{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/defaultValue}}{{/required}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/nullableDataType.mustache b/boat-scaffold/src/main/templates/boat-spring/nullableDataType.mustache index ba9bb9463..fd94338fc 100644 --- a/boat-scaffold/src/main/templates/boat-spring/nullableDataType.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/nullableDataType.mustache @@ -1 +1 @@ -{{#openApiNullable}}{{#isNullable}}JsonNullable<{{{datatypeWithEnum}}}>{{/isNullable}}{{^isNullable}}{{{datatypeWithEnum}}}{{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}{{{datatypeWithEnum}}}{{/openApiNullable}} \ No newline at end of file +{{#openApiNullable}}{{#isNullable}}JsonNullable<{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional<{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}}{{{datatypeWithEnum}}}{{#openApiNullable}}{{#isNullable}}>{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}>{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache b/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache new file mode 100644 index 000000000..8d6fc6dec --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache @@ -0,0 +1 @@ +{{#openApiNullable}}{{#isNullable}}{{^isContainer}}JsonNullable<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{#isContainer}}JsonNullable<{{/isContainer}}{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}}{{{datatypeWithEnum}}}{{#openApiNullable}}{{#isNullable}}>{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}>{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/oneof_interface.mustache b/boat-scaffold/src/main/templates/boat-spring/oneof_interface.mustache index 679fe3d88..00fca619c 100644 --- a/boat-scaffold/src/main/templates/boat-spring/oneof_interface.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/oneof_interface.mustache @@ -1,12 +1,24 @@ {{>additionalOneOfTypeAnnotations}} {{#withXml}} {{>xmlAnnotation}} + {{/withXml}} {{#discriminator}} {{>typeInfoAnnotation}} + +{{/discriminator}}{{^discriminator}}{{#useDeductionForOneOfInterfaces}} +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes({ + {{#interfaceModels}} + @JsonSubTypes.Type(value = {{classname}}.class){{^-last}}, {{/-last}} + {{/interfaceModels}} +}) +{{/useDeductionForOneOfInterfaces}}{{#vendorExtensions.x-class-extra-annotation}}{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} {{/discriminator}} {{>generatedAnnotation}} -public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + +public {{>sealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>permits}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/boat-scaffold/src/main/templates/boat-spring/openapiDocumentationConfig.mustache b/boat-scaffold/src/main/templates/boat-spring/openapiDocumentationConfig.mustache index bae72f1cc..cb0d595d9 100644 --- a/boat-scaffold/src/main/templates/boat-spring/openapiDocumentationConfig.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/openapiDocumentationConfig.mustache @@ -18,19 +18,14 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2; {{#useOptional}} import java.util.Optional; {{/useOptional}} -{{#useJakartaEe}} -import jakarta.annotation.Generated; -import jakarta.servlet.ServletContext; -{{/useJakartaEe}} -{{^useJakartaEe}} -import javax.annotation.Generated; -import javax.servlet.ServletContext; -{{/useJakartaEe}} +import {{javaxPackage}}.annotation.Generated; +import {{javaxPackage}}.servlet.ServletContext; {{>generatedAnnotation}} + @Configuration @EnableSwagger2 -public class OpenAPIDocumentationConfig { +public class SpringFoxConfiguration { ApiInfo apiInfo() { return new ApiInfoBuilder() @@ -86,4 +81,4 @@ public class OpenAPIDocumentationConfig { } } -} \ No newline at end of file +} diff --git a/boat-scaffold/src/main/templates/boat-spring/optionalDataType.mustache b/boat-scaffold/src/main/templates/boat-spring/optionalDataType.mustache index ec9df1a76..84505f8fc 100644 --- a/boat-scaffold/src/main/templates/boat-spring/optionalDataType.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/optionalDataType.mustache @@ -1,13 +1 @@ -{{#toOneLine}} - {{#useOptional}} - {{#required}} - {{>collectionDataTypeParam}} - {{/required}} - {{^required}} - Optional<{{>collectionDataTypeParam}}> - {{/required}} - {{/useOptional}} - {{^useOptional}} - {{>collectionDataTypeParam}} - {{/useOptional}} -{{/toOneLine}} \ No newline at end of file +{{#useOptional}}{{#required}}{{{dataType}}}{{/required}}{{^required}}Optional<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{{dataType}}}>{{/required}}{{/useOptional}}{{^useOptional}}{{{dataType}}}{{/useOptional}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/paramDoc.mustache b/boat-scaffold/src/main/templates/boat-spring/paramDoc.mustache index 7c0c65eb1..c546f9c4e 100644 --- a/boat-scaffold/src/main/templates/boat-spring/paramDoc.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/paramDoc.mustache @@ -1 +1 @@ -{{#swagger2AnnotationLibrary}}@Parameter(name = "{{{baseName}}}", description = "{{{description}}}"{{#required}}, required = true{{/required}}){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, {{> allowableValues }}{{/allowableValues}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/swagger1AnnotationLibrary}} \ No newline at end of file +{{#swagger2AnnotationLibrary}}@Parameter(name = "{{{baseName}}}"{{#isDeprecated}}, deprecated = true{{/isDeprecated}}, description = "{{{description}}}"{{#required}}, required = true{{/required}}{{#isPathParam}}, in = ParameterIn.PATH{{/isPathParam}}{{#isQueryParam}}, in = ParameterIn.QUERY{{/isQueryParam}}{{#isCookieParam}}, in = ParameterIn.COOKIE{{/isCookieParam}}{{#isHeaderParam}}, in = ParameterIn.HEADER{{/isHeaderParam}}){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}}@ApiParam(value = "{{{description}}}"{{#required}}, required = true{{/required}}{{#allowableValues}}, {{> allowableValues }}{{/allowableValues}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/swagger1AnnotationLibrary}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/pathParams.mustache b/boat-scaffold/src/main/templates/boat-spring/pathParams.mustache index 16888518c..24ebb856a 100644 --- a/boat-scaffold/src/main/templates/boat-spring/pathParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/pathParams.mustache @@ -1 +1 @@ -{{#isPathParam}}{{#useBeanValidation}}{{>beanValidationPathParams}}{{/useBeanValidation}}{{>paramDoc}} @PathVariable("{{baseName}}"){{>dateTimeParam}} {{>optionalDataType}} {{paramName}}{{/isPathParam}} \ No newline at end of file +{{#isPathParam}}{{#vendorExtensions.x-field-extra-annotation}}{{{.}}} {{/vendorExtensions.x-field-extra-annotation}}{{#useBeanValidation}}{{>beanValidationPathParams}}{{/useBeanValidation}}{{>paramDoc}} @PathVariable("{{baseName}}"){{>dateTimeParam}}{{#isDeprecated}} @Deprecated{{/isDeprecated}} {{>optionalDataType}} {{paramName}}{{/isPathParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/permits.mustache b/boat-scaffold/src/main/templates/boat-spring/permits.mustache new file mode 100644 index 000000000..9583d443a --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/permits.mustache @@ -0,0 +1 @@ +{{#useSealed}}{{#permits}}{{#-first}}permits {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{#-last}} {{/-last}}{{/permits}}{{/useSealed}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/pojo.mustache b/boat-scaffold/src/main/templates/boat-spring/pojo.mustache index ed55202ae..82a0a6eb0 100644 --- a/boat-scaffold/src/main/templates/boat-spring/pojo.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/pojo.mustache @@ -1,369 +1,363 @@ /** -* {{description}}{{^description}}{{classname}}{{/description}} -*/ + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */ {{>additionalModelTypeAnnotations}} + +{{#isDeprecated}} +@Deprecated +{{/isDeprecated}} {{#description}} - {{#swagger1AnnotationLibrary}} - @ApiModel(description = "{{{description}}}") - {{/swagger1AnnotationLibrary}} - {{#swagger2AnnotationLibrary}} - @Schema({{#name}}name = "{{name}}", {{/name}}description = "{{{description}}}") - {{/swagger2AnnotationLibrary}} +{{#swagger1AnnotationLibrary}} +@ApiModel(description = "{{{description}}}") +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +@Schema({{#name}}name = "{{name}}", {{/name}}description = "{{{description}}}"{{#deprecated}}, deprecated = true{{/deprecated}}) +{{/swagger2AnnotationLibrary}} {{/description}} {{#discriminator}} - {{>typeInfoAnnotation}} +{{>typeInfoAnnotation}} + {{/discriminator}} {{#jackson}} - {{#isClassnameSanitized}} - {{^hasDiscriminatorWithNonEmptyMapping}} - @JsonTypeName("{{name}}") - {{/hasDiscriminatorWithNonEmptyMapping}} - {{/isClassnameSanitized}} +{{#isClassnameSanitized}} +{{^hasDiscriminatorWithNonEmptyMapping}} +@JsonTypeName("{{name}}") +{{/hasDiscriminatorWithNonEmptyMapping}} +{{/isClassnameSanitized}} {{/jackson}} {{#withXml}} - {{>xmlAnnotation}} +{{>xmlAnnotation}} + {{/withXml}} {{>generatedAnnotation}} -{{#useLombokAnnotations}} -@lombok.EqualsAndHashCode(onlyExplicitlyIncluded = true, doNotUseGetters = true{{#parent}}, callSuper = true{{/parent}}) -@lombok.ToString(onlyExplicitlyIncluded = true, doNotUseGetters = true{{#parent}}, callSuper = true{{/parent}}) -{{/useLombokAnnotations}} + {{#vendorExtensions.x-class-extra-annotation}} - {{{vendorExtensions.x-class-extra-annotation}}} +{{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { +public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>permits}}{ {{#serializableModel}} private static final long serialVersionUID = 1L; {{/serializableModel}} -{{#vars}} + {{#vars}} - {{#isEnum}} + {{#isEnum}} {{^isContainer}} - {{>enumClass}} +{{>enumClass}} + {{/isContainer}} {{#isContainer}} - {{#mostInnerItems}} - {{>enumClass}} - {{/mostInnerItems}} + {{#mostInnerItems}} +{{>enumClass}} + + {{/mostInnerItems}} {{/isContainer}} - {{/isEnum}} - {{#jackson}} - {{#withXml}} - @JacksonXmlProperty({{#isXmlAttribute}}isAttribute = true, {{/isXmlAttribute}}{{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/withXml}} - {{/jackson}} + {{/isEnum}} {{#gson}} - @SerializedName("{{baseName}}") + @SerializedName("{{baseName}}") {{/gson}} - {{#useLombokAnnotations}} - @lombok.Getter{{#swagger1AnnotationLibrary}}(onMethod_ = @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}")){{/swagger1AnnotationLibrary}} - @lombok.Setter - @lombok.EqualsAndHashCode.Include - @lombok.ToString.Include - {{#vendorExtensions.x-extra-annotation}} - {{#indent4}}{{{vendorExtensions.x-extra-annotation}}}{{/indent4}}{{/vendorExtensions.x-extra-annotation}} - {{/useLombokAnnotations}} +{{>lombokAnnotation}} {{#vendorExtensions.x-field-extra-annotation}} - {{{vendorExtensions.x-field-extra-annotation}}} + {{{.}}} {{/vendorExtensions.x-field-extra-annotation}} - {{#trimAndIndent4}} + {{#deprecated}} + @Deprecated + {{/deprecated}} {{#isContainer}} - {{#useBeanValidation}} - @Valid - {{#required}} - @NotNull - {{/required}} - {{>beanValidationCore}} - {{/useBeanValidation}} - {{#isMap}} - {{#openApiNullable}} - private {{#toOneLine}}{{>mapDataType}}{{/toOneLine}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; - {{/openApiNullable}} - {{^openApiNullable}} - private {{#toOneLine}}{{>mapDataType}}{{/toOneLine}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/openApiNullable}} - {{/isMap}} - {{^isMap}} - {{#isArray}} - {{#openApiNullable}} - private {{#toOneLine}}{{>collectionDataType}}{{/toOneLine}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; - {{/openApiNullable}} - {{^openApiNullable}} - private {{#toOneLine}}{{>collectionDataType}}{{/toOneLine}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/openApiNullable}} - {{/isArray}} - {{^isArray}} - {{#openApiNullable}} - private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; - {{/openApiNullable}} - {{^openApiNullable}} - private {{>nullableDataType}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/openApiNullable}} - {{/isArray}} - {{/isMap}} + {{#useBeanValidation}}@Valid{{/useBeanValidation}} + {{#openApiNullable}} + private {{>nullableAnnotation}}{{#isNullable}}{{>nullableDataTypeBeanValidation}} {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();{{/isNullable}}{{^required}}{{^isNullable}}{{>nullableDataTypeBeanValidation}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/isNullable}}{{/required}}{{#required}}{{^isNullable}}{{>nullableDataTypeBeanValidation}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/isNullable}}{{/required}} + {{/openApiNullable}} + {{^openApiNullable}} + private {{>nullableAnnotation}}{{>nullableDataType}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/openApiNullable}} {{/isContainer}} {{^isContainer}} - {{#isDate}} - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - {{/isDate}} - {{#isDateTime}} - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - {{/isDateTime}} - {{#openApiNullable}} - {{#required}} - @NotNull - {{/required}} - {{#useBeanValidation}} - @Valid - {{#required}} - @NotNull - {{/required}} - {{>beanValidationCore}} - {{/useBeanValidation}} - private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; - {{/openApiNullable}} - {{^openApiNullable}} - {{#useBeanValidation}} - @Valid - {{#required}} - @NotNull - {{/required}} - {{>beanValidationCore}} - {{/useBeanValidation}} - private {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; - {{/openApiNullable}} + {{#isDate}} + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + {{/isDate}} + {{#isDateTime}} + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + {{/isDateTime}} + {{#openApiNullable}} + private {{>nullableAnnotation}}{{#isNullable}}{{>nullableDataTypeBeanValidation}} {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();{{/isNullable}}{{^required}}{{^isNullable}}{{>nullableDataTypeBeanValidation}} {{name}}{{#useOptional}} = Optional.{{^defaultValue}}empty(){{/defaultValue}}{{#defaultValue}}of({{{.}}}){{/defaultValue}};{{/useOptional}}{{^useOptional}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/useOptional}}{{/isNullable}}{{/required}}{{^isNullable}}{{#required}}{{>nullableDataTypeBeanValidation}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/required}}{{/isNullable}} + {{/openApiNullable}} + {{^openApiNullable}} + private {{>nullableAnnotation}}{{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; + {{/openApiNullable}} {{/isContainer}} - {{/trimAndIndent4}} -{{/vars}} -{{#generatedConstructorWithRequiredArgs}} - {{#hasRequired}} + {{/vars}} + {{#vendorExtensions.x-java-no-args-constructor}} - public {{classname}}() { + public {{classname}}() { super(); - } + } + {{/vendorExtensions.x-java-no-args-constructor}} + {{^lombok.Data}} + {{^lombok.RequiredArgsConstructor}} + {{#generatedConstructorWithRequiredArgs}} + {{#hasRequired}} - /** - * Constructor with only required parameters - */ - public {{classname}}({{#requiredVars}}{{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/requiredVars}}) { + /** + * Constructor with only required parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} + */ + public {{classname}}({{#requiredVars}}{{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/requiredVars}}) { {{#parent}} - super({{#parentRequiredVars}}{{name}}{{^-last}}, {{/-last}}{{/parentRequiredVars}}); + super({{#parentRequiredVars}}{{name}}{{^-last}}, {{/-last}}{{/parentRequiredVars}}); {{/parent}} {{#vars}} - {{#required}} - {{#openApiNullable}} - this.{{name}} = {{#isNullable}}JsonNullable.of({{name}}){{/isNullable}}{{^isNullable}}{{name}}{{/isNullable}}; - {{/openApiNullable}} - {{^openApiNullable}} - this.{{name}} = {{name}}; - {{/openApiNullable}} - {{/required}} + {{#required}} + {{#openApiNullable}} + this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional.ofNullable({{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{name}}{{#isNullable}}){{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}){{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}; + {{/openApiNullable}} + {{^openApiNullable}} + this.{{name}} = {{name}}; + {{/openApiNullable}} + {{/required}} {{/vars}} - } + } {{/hasRequired}} -{{/generatedConstructorWithRequiredArgs}} -{{#vars}} + {{/generatedConstructorWithRequiredArgs}} + {{/lombok.RequiredArgsConstructor}} + {{#vendorExtensions.x-java-all-args-constructor}} - {{! begin feature: fluent setter methods }} - public {{classname}} {{#useWithModifiers}}with{{nameInCamelCase}}{{/useWithModifiers}}{{^useWithModifiers}}{{name}}{{/useWithModifiers}}({{{datatypeWithEnum}}} {{name}}) { - {{#openApiNullable}} - this.{{name}} = {{#isNullable}}JsonNullable.of({{name}}){{/isNullable}}{{^isNullable}}{{name}}{{/isNullable}}; + /** + * Constructor with all args parameters + */ + public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{>nullableAnnotation}}{{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { + {{#parent}} + super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); + {{/parent}} + {{#vars}} + {{#openApiNullable}} + this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional.ofNullable({{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{name}}{{#isNullable}}){{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}){{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}; {{/openApiNullable}} {{^openApiNullable}} - this.{{name}} = {{name}}; + this.{{name}} = {{name}}; {{/openApiNullable}} - return this; + {{/vars}} + } + {{/vendorExtensions.x-java-all-args-constructor}} + {{/lombok.Data}} + {{#vars}} + {{^lombok.Data}} + + {{! begin feature: fluent setter methods }} + public {{classname}} {{name}}({{>nullableAnnotation}}{{{datatypeWithEnum}}} {{name}}) { + {{#openApiNullable}} + this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional.of{{#optionalAcceptNullable}}Nullable{{/optionalAcceptNullable}}({{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{name}}{{#isNullable}}){{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}){{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}; + {{/openApiNullable}} + {{^openApiNullable}} + this.{{name}} = {{name}}; + {{/openApiNullable}} + return this; } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { {{#openApiNullable}} - if (this.{{name}} == null{{#isNullable}} || !this.{{name}}.isPresent(){{/isNullable}}) { + if (this.{{name}} == null{{#isNullable}} || !this.{{name}}.isPresent(){{/isNullable}}) { this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}{{#isNullable}}){{/isNullable}}; - } - this.{{name}}{{#isNullable}}.get(){{/isNullable}}.add({{name}}Item); + } + this.{{name}}{{#isNullable}}.get(){{/isNullable}}.add({{name}}Item); {{/openApiNullable}} {{^openApiNullable}} - if (this.{{name}} == null) { + if (this.{{name}} == null) { this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; - } - this.{{name}}.add({{name}}Item); + } + this.{{name}}.add({{name}}Item); {{/openApiNullable}} return this; - } + } {{/isArray}} {{#isMap}} + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { {{#openApiNullable}} - {{#isNullable}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - {{^required}} - if (this.{{name}} == null) { - this.{{name}} = JsonNullable.of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); - } - {{/required}} - this.{{name}}.get().put(key, {{name}}Item); - return this; - } - {{/isNullable}} - {{^isNullable}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - {{^required}} - - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; - } - {{/required}} - this.{{name}}.put(key, {{name}}Item); - return this; - } - {{/isNullable}} + if (this.{{name}} == null{{#isNullable}} || !this.{{name}}.isPresent(){{/isNullable}}) { + this.{{name}} = {{#isNullable}}JsonNullable.of({{/isNullable}}{{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}HashMap{{/uniqueItems}}<>(){{/defaultValue}}{{#isNullable}}){{/isNullable}}; + } + this.{{name}}{{#isNullable}}.get(){{/isNullable}}.put(key, {{name}}Item); {{/openApiNullable}} {{^openApiNullable}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - {{^required}} - - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; - } - {{/required}} - this.{{name}}.put(key, {{name}}Item); - return this; - } + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}HashMap{{/uniqueItems}}<>(){{/defaultValue}}; + } + this.{{name}}.put(key, {{name}}Item); {{/openApiNullable}} + return this; + } {{/isMap}} {{! end feature: fluent setter methods }} {{! begin feature: getter and setter }} + {{^lombok.Getter}} -{{^useLombokAnnotations}} /** {{#description}} - * {{{.}}} + * {{{.}}} {{/description}} {{^description}} - * Get {{name}} + * Get {{name}} {{/description}} {{#minimum}} - * minimum: {{.}} + * minimum: {{.}} {{/minimum}} {{#maximum}} - * maximum: {{.}} + * maximum: {{.}} {{/maximum}} - * @return {{name}} - */ + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ {{#vendorExtensions.x-extra-annotation}} - {{{vendorExtensions.x-extra-annotation}}} + {{{vendorExtensions.x-extra-annotation}}} {{/vendorExtensions.x-extra-annotation}} {{#useBeanValidation}} - {{>beanValidation}} + {{>beanValidation}}{{! prevent indent}} {{/useBeanValidation}} {{^useBeanValidation}} - {{#required}}@NotNull{{/required}} + {{#required}}@NotNull{{/required}} {{/useBeanValidation}} {{#swagger2AnnotationLibrary}} - @Schema(name = "{{{baseName}}}"{{#isReadOnly}}, accessMode = Schema.AccessMode.READ_ONLY{{/isReadOnly}}{{#example}}, example = "{{{.}}}"{{/example}}{{#description}}, description = "{{{.}}}"{{/description}}, requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}) + @Schema(name = "{{{baseName}}}"{{#isReadOnly}}, accessMode = Schema.AccessMode.READ_ONLY{{/isReadOnly}}{{#example}}, example = "{{{.}}}"{{/example}}{{#description}}, description = "{{{.}}}"{{/description}}{{#deprecated}}, deprecated = true{{/deprecated}}, requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}) {{/swagger2AnnotationLibrary}} {{#swagger1AnnotationLibrary}} - @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}") + @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}") {{/swagger1AnnotationLibrary}} {{#jackson}} - @JsonProperty("{{baseName}}") + @JsonProperty("{{baseName}}") + {{#withXml}} + @JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isContainer}} + @JacksonXmlElementWrapper({{#isXmlWrapped}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}", {{#xmlNamespace}}namespace = "{{.}}", {{/xmlNamespace}}{{/isXmlWrapped}}useWrapping = {{isXmlWrapped}}) + {{/isContainer}} + {{/withXml}} {{/jackson}} - public {{>nullableDataType}} {{getter}}() { - return {{name}}; + {{#withXml}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{/withXml}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{>nullableAnnotation}}{{>nullableDataTypeBeanValidation}} {{getter}}() { + return {{name}}; } + {{/lombok.Getter}} + {{^lombok.Setter}} + {{#deprecated}} + /** + * @deprecated + */ + {{/deprecated}} {{#vendorExtensions.x-setter-extra-annotation}} - {{{vendorExtensions.x-setter-extra-annotation}}} + {{{vendorExtensions.x-setter-extra-annotation}}} {{/vendorExtensions.x-setter-extra-annotation}} - public void {{setter}}({{>nullableDataType}} {{name}}) { - this.{{name}} = {{name}}; + {{#deprecated}} + @Deprecated + {{/deprecated}} + public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { + this.{{name}} = {{name}}; } - {{/useLombokAnnotations}} + {{/lombok.Setter}} + {{/lombok.Data}} {{! end feature: getter and setter }} {{/vars}} +{{>additionalProperties}} + + {{^lombok.Data}} {{#parentVars}} -{{! begin feature: fluent setter methods for inherited properties }} + {{^lombok.Setter}} + {{! begin feature: fluent setter methods for inherited properties }} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { - super.{{setter}}({{name}}); - return this; + super.{{name}}({{name}}); + return this; } {{#isArray}} - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { - super.add{{nameInCamelCase}}Item({{name}}Item); + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + super.add{{nameInPascalCase}}Item({{name}}Item); return this; - } + } {{/isArray}} {{#isMap}} - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - super.put{{nameInCamelCase}}Item(key, {{name}}Item); + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + super.put{{nameInPascalCase}}Item(key, {{name}}Item); return this; - } + } {{/isMap}} -{{! end feature: fluent setter methods for inherited properties }} -{{/parentVars}} - -{{^useLombokAnnotations}} + {{/lombok.Setter}} + {{! end feature: fluent setter methods for inherited properties }} + {{/parentVars}} + {{^lombok.EqualsAndHashCode}} @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - }{{#hasVars}} + 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 true;{{/hasVars}} - } - {{#vendorExtensions.x-jackson-optional-nullable-helpers}} + {{/-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}} + }{{#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}} + 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() { - 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}}); - } - {{#vendorExtensions.x-jackson-optional-nullable-helpers}} + @Override + public int hashCode() { + 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}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/additionalPropertiesType}}); + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - private static int hashCodeNullable(JsonNullable a) { - if (a == null) { + 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}} + } + return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + {{/lombok.EqualsAndHashCode}} - @Override - public String toString() { + {{^lombok.ToString}} + @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("}"); + {{#parent}} + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + {{/parent}} + {{#vars}}sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).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) { + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString({{>nullableAnnotation}}Object o) { if (o == null) { - return "null"; + return "null"; } return o.toString().replace("\n", "\n "); - } - {{/useLombokAnnotations}} -} \ No newline at end of file + } + {{/lombok.ToString}} + {{#generateBuilders}} + {{>javaBuilder}}{{! prevent indent}} + {{/generateBuilders}} + {{/lombok.Data}} +} diff --git a/boat-scaffold/src/main/templates/boat-spring/project/build.properties b/boat-scaffold/src/main/templates/boat-spring/project/build.properties index a8c2f849b..cc68b53f1 100644 --- a/boat-scaffold/src/main/templates/boat-spring/project/build.properties +++ b/boat-scaffold/src/main/templates/boat-spring/project/build.properties @@ -1 +1 @@ -sbt.version=0.12.0 +sbt.version=1.10.11 diff --git a/boat-scaffold/src/main/templates/boat-spring/queryParams.mustache b/boat-scaffold/src/main/templates/boat-spring/queryParams.mustache index 6b78fb343..56f7527eb 100644 --- a/boat-scaffold/src/main/templates/boat-spring/queryParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/queryParams.mustache @@ -1 +1 @@ -{{#isQueryParam}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}}{{^isModel}} @RequestParam(value = {{#isMap}}""{{/isMap}}{{^isMap}}"{{baseName}}"{{/isMap}}{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/isModel}}{{>dateTimeParam}} {{>optionalDataType}} {{paramName}}{{/isQueryParam}} \ No newline at end of file +{{#isQueryParam}}{{#vendorExtensions.x-field-extra-annotation}}{{{.}}} {{/vendorExtensions.x-field-extra-annotation}}{{#useBeanValidation}}{{>beanValidationQueryParams}}{{/useBeanValidation}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}}{{^isModel}} @RequestParam(value = {{#isMap}}""{{/isMap}}{{^isMap}}"{{baseName}}"{{/isMap}}{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}{{#defaultValue}}, defaultValue = "{{{.}}}"{{/defaultValue}}){{/isModel}}{{>dateTimeParam}}{{#isDeprecated}} @Deprecated{{/isDeprecated}} {{>nullableAnnotation}}{{>optionalDataType}} {{paramName}}{{/isQueryParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/responseType.mustache b/boat-scaffold/src/main/templates/boat-spring/responseType.mustache index 305f28e6d..a25da6310 100644 --- a/boat-scaffold/src/main/templates/boat-spring/responseType.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/responseType.mustache @@ -1 +1 @@ -{{^vendorExtensions.x-sse}}{{#reactive}}{{#useResponseEntity}}MonoreturnTypes}}>>{{/useResponseEntity}}{{^useResponseEntity}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/useResponseEntity}}{{/reactive}}{{^reactive}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}}{{/reactive}}{{/vendorExtensions.x-sse}}{{#vendorExtensions.x-sse}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/vendorExtensions.x-sse}} \ No newline at end of file +{{^vendorExtensions.x-sse}}{{#reactive}}{{#useResponseEntity}}MonoreturnTypes}}{{#isArray}}>{{/isArray}}>>{{/useResponseEntity}}{{^useResponseEntity}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/useResponseEntity}}{{/reactive}}{{^reactive}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}}{{/reactive}}{{/vendorExtensions.x-sse}}{{#vendorExtensions.x-sse}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/vendorExtensions.x-sse}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/returnTypes.mustache b/boat-scaffold/src/main/templates/boat-spring/returnTypes.mustache index 0d2b380d7..757696365 100644 --- a/boat-scaffold/src/main/templates/boat-spring/returnTypes.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/returnTypes.mustache @@ -1 +1 @@ -{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}} \ No newline at end of file +{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}{{{returnType}}}{{/reactive}}{{^reactive}}{{#generateGenericResponseEntity}}?{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{{returnContainer}}}<{{{returnType}}}>{{/generateGenericResponseEntity}}{{/reactive}}{{/isArray}}{{^returnContainer}}{{#useResponseEntity}}{{#generateGenericResponseEntity}}?{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{{returnType}}}{{/generateGenericResponseEntity}}{{/useResponseEntity}}{{^useResponseEntity}}{{#isDelegate}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/isDelegate}}{{^isDelegate}}{{#async}}{{{returnType}}}{{/async}}{{^async}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/async}}{{/isDelegate}}{{/useResponseEntity}}{{/returnContainer}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/sealed.mustache b/boat-scaffold/src/main/templates/boat-spring/sealed.mustache new file mode 100644 index 000000000..a5c0af002 --- /dev/null +++ b/boat-scaffold/src/main/templates/boat-spring/sealed.mustache @@ -0,0 +1 @@ +{{#useSealed}}{{#permits.0}}sealed {{/permits.0}}{{^permits.0}}{{^vendorExtensions.x-is-one-of-interface}}final {{/vendorExtensions.x-is-one-of-interface}}{{/permits.0}}{{/useSealed}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/springdocDocumentationConfig.mustache b/boat-scaffold/src/main/templates/boat-spring/springdocDocumentationConfig.mustache index 467d92155..b0509f8d4 100644 --- a/boat-scaffold/src/main/templates/boat-spring/springdocDocumentationConfig.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/springdocDocumentationConfig.mustache @@ -13,7 +13,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme; @Configuration public class SpringDocConfiguration { - @Bean + @Bean(name = "{{configPackage}}.SpringDocConfiguration.apiInfo") OpenAPI apiInfo() { return new OpenAPI() .info( diff --git a/boat-scaffold/src/main/templates/boat-spring/xmlAnnotation.mustache b/boat-scaffold/src/main/templates/boat-spring/xmlAnnotation.mustache index a9e6fb0fa..b3a89fa36 100644 --- a/boat-scaffold/src/main/templates/boat-spring/xmlAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/xmlAnnotation.mustache @@ -1,7 +1,6 @@ {{#withXml}} {{#jackson}} -@JacksonXmlRootElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") +@JacksonXmlRootElement({{#xmlNamespace}}namespace = "{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") {{/jackson}} -@XmlRootElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") -@XmlAccessorType(XmlAccessType.FIELD) -{{/withXml}} \ No newline at end of file +@XmlRootElement({{#xmlNamespace}}namespace = "{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") +@XmlAccessorType(XmlAccessType.FIELD){{/withXml}} \ No newline at end of file diff --git a/boat-trail-resources/pom.xml b/boat-trail-resources/pom.xml index 1f16f16f8..b3e0c7f8f 100644 --- a/boat-trail-resources/pom.xml +++ b/boat-trail-resources/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT boat-trail-resources diff --git a/pom.xml b/pom.xml index 2d8706544..bce873435 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT pom Backbase Open Api Tools is a collection of tools to work with Open API @@ -44,7 +44,7 @@ backbase https://sonarcloud.io - 7.4.0 + 7.20.0 tests/target/site/jacoco-aggregate/jacoco.xml ${aggregate.report.dir} diff --git a/tests/pom.xml b/tests/pom.xml index fc120697c..90eee5d9c 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -5,7 +5,7 @@ com.backbase.oss backbase-openapi-tools - 0.17.75-SNAPSHOT + 0.18.0-SNAPSHOT tests From 65e3a345ab049c17528279ac0ed8fd7304d5d3f6 Mon Sep 17 00:00:00 2001 From: marcinm_backbase <63855724+bbzurek@users.noreply.github.com> Date: Tue, 24 Mar 2026 07:30:59 +0100 Subject: [PATCH 2/5] remove obsolete features update default templates to support remaining functionality --- README.md | 11 +- boat-maven-plugin/README.md | 6 +- boat-maven-plugin/pom.xml | 2 +- .../boat-generate/java-clients/java/pom.xml | 2 +- .../boat-generate/java-server/models/pom.xml | 5 + boat-maven-plugin/src/it/example/pom.xml | 12 +- .../oss/boat/AbstractGenerateMojo.java | 16 +- .../com/backbase/oss/boat/GenerateMojo.java | 4 +- .../GenerateRestTemplateEmbeddedMojo.java | 2 +- .../backbase/oss/boat/GenerateMojoTests.java | 4 +- .../oss/boat/radio/RadioMojoTests.java | 5 +- boat-scaffold/README.md | 7 - .../oss/codegen/java/BoatJavaCodeGen.java | 71 +--- .../oss/codegen/java/BoatSpringCodeGen.java | 141 +++---- .../java/BoatSpringCodegenProperty.java | 2 +- .../oss/codegen/java/BoatWebhooksCodeGen.java | 10 - .../js_json_schema_ref_parser.mustache | 4 +- .../libraries/resttemplate/ApiClient.mustache | 5 +- .../main/templates/boat-spring/api.mustache | 8 +- .../boat-spring/beanValidation.mustache | 2 +- .../templates/boat-spring/bodyParams.mustache | 2 +- .../boat-spring/mapDataType.mustache | 21 +- .../boat-spring/nullableAnnotation.mustache | 2 +- .../nullableDataTypeBeanValidation.mustache | 2 +- .../main/templates/boat-webhooks/api.mustache | 3 + .../codegen/java/BoatJavaCodeGenTests.java | 44 +-- .../codegen/java/BoatSpringCodeGenTests.java | 67 ++-- .../java/BoatSpringTemplatesTests.java | 367 ++++++++++-------- .../oas-examples/petstore-example-refs.yaml | 2 +- 29 files changed, 404 insertions(+), 425 deletions(-) diff --git a/README.md b/README.md index 5fe9e74ec..d466e37ac 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ It currently consists of # Release Notes BOAT is still under development and subject to change. +## 0.18.0 +* openapi-generator `7.20.0` baseline (Spring Boot 4, Jackson 3) +* moved Java and JavaSpring templates to `7.20.0` adding remaing custom features +* removed custom `createApiComponent` in favor of the standard `generateClientAsBean` with default set to `true` +* removed custom `useLombokAnnotations` in favor of built-in `lombokAnnotation` template +* removed custom `useClassLevelBeanValidation` in favor of the standard `useSpringBuiltInValidation` property +* removed custom `useWithModifiers`, fluent setters already included in generated classes (property name methods) +* removed unused `useProtectedFields` property +* removed unused `useDefaultApiClient` property + ## 0.17.75 * Fixed duplicate serialization of the discriminator property in Jackson-based Java models by removing allowGetters = true from the @JsonIgnoreProperties annotation. * In Spring generator added support for type-level validation in collections via the `x-not-null` vendor extension to allow `@NotNull` annotations on generic type arguments. @@ -725,7 +735,6 @@ For the `spring` generator, the additional configuration options are: | `useTags` | use tags for creating interface and controller classnames (Default: false) | | `useBeanValidation` | Use BeanValidation API annotations (Default: true) | | `performBeanValidation` | Use Bean Validation Impl. to perform BeanValidation (Default: false) | -| `useClassLevelBeanValidation` | Adds @Validated annotation to API interfaces (Default: false) | | `useLombokAnnotations` | Use Lombok annotations to generate properties accessors and `hashCode`/`equals` methods (Default: false) | | `addServletRequest` | Adds ServletRequest objects to API method definitions (Default: false) | | `addBindingResult` | Adds BindingResult to Api method definitions' request bodies if UseBeanValidation true, for this to be effective you must configure UseBeanValidation, this is not done automatically (Default: false)| diff --git a/boat-maven-plugin/README.md b/boat-maven-plugin/README.md index 3197e9d3c..67b2202a5 100644 --- a/boat-maven-plugin/README.md +++ b/boat-maven-plugin/README.md @@ -37,7 +37,6 @@ Same with `generate` but with opinionated defaults for Spring true true true - false true true false @@ -78,7 +77,6 @@ It will generate webhook interfaces with prehook and posthook request mapping fo true true true - false true true false @@ -108,7 +106,6 @@ Same with `generate` but with opinionated defaults for Rest Template Client true true true - false true true false @@ -122,7 +119,7 @@ Same with `generate` but with opinionated defaults for Rest Template Client ... - createApiComponent=false + restTemplateBeanName=interServiceRestTemplate @@ -198,7 +195,6 @@ Same with `generate` but with opinionated defaults for Web Client true true true - false true true false diff --git a/boat-maven-plugin/pom.xml b/boat-maven-plugin/pom.xml index d60c02d6f..80caf12ad 100644 --- a/boat-maven-plugin/pom.xml +++ b/boat-maven-plugin/pom.xml @@ -360,7 +360,7 @@ com.backbase.oss boat-maven-plugin - 0.18.0 + 0.17.74 diff --git a/boat-maven-plugin/src/it/example/boat-generate/java-clients/java/pom.xml b/boat-maven-plugin/src/it/example/boat-generate/java-clients/java/pom.xml index 225944b0a..03dff88ab 100644 --- a/boat-maven-plugin/src/it/example/boat-generate/java-clients/java/pom.xml +++ b/boat-maven-plugin/src/it/example/boat-generate/java-clients/java/pom.xml @@ -15,7 +15,7 @@ BOAT :: Generate :: Java Client - ApiClient.java,BeanValidationException.java,RFC3339DateFormat.java,ServerConfiguration.java,ServerVariable.java,StringUtil.java,Authentication.java,HttpBasicAuth.java,HttpBearerAuth.java,ApiKeyAuth.java,ApiException.java,Pair.java,ApiResponse.java,JavaTimeFormatter.java + ApiClient.java,BeanValidationException.java,RFC3339DateFormat.java,ServerConfiguration.java,ServerVariable.java,StringUtil.java,Authentication.java,HttpBasicAuth.java,HttpBearerAuth.java,ApiKeyAuth.java,ApiException.java,Pair.java,ApiResponse.java,JavaTimeFormatter.java,RFC3339JavaTimeModule.java,Configuration.java,RFC3339InstantDeserializer.java,JSON.java,AbstractOpenApiSchema.java diff --git a/boat-maven-plugin/src/it/example/boat-generate/java-server/models/pom.xml b/boat-maven-plugin/src/it/example/boat-generate/java-server/models/pom.xml index d642577db..3b14a7f7e 100644 --- a/boat-maven-plugin/src/it/example/boat-generate/java-server/models/pom.xml +++ b/boat-maven-plugin/src/it/example/boat-generate/java-server/models/pom.xml @@ -73,6 +73,11 @@ swagger-annotations 2.2.7 + + + org.springframework + spring-core + jar diff --git a/boat-maven-plugin/src/it/example/pom.xml b/boat-maven-plugin/src/it/example/pom.xml index 650561ff3..dc646eeb3 100644 --- a/boat-maven-plugin/src/it/example/pom.xml +++ b/boat-maven-plugin/src/it/example/pom.xml @@ -11,10 +11,10 @@ @pom.version@ - - 0.2.3 + + 0.2.9 - 2.13.3 + 2.18.2 ${jackson.version} ${jackson.version} ${jackson.version} @@ -135,7 +135,7 @@ org.springframework.boot spring-boot-starter-webflux - 2.7.2 + 3.3.13 @@ -147,13 +147,13 @@ org.springframework.boot spring-boot-starter-web - 2.7.2 + 3.3.13 org.springframework spring-core - 6.1.14 + 6.1.21 compile diff --git a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/AbstractGenerateMojo.java b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/AbstractGenerateMojo.java index 703529a0c..49da05e8f 100644 --- a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/AbstractGenerateMojo.java +++ b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/AbstractGenerateMojo.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.openapitools.codegen.languages.SpringCodegen; @Slf4j public abstract class AbstractGenerateMojo extends GenerateMojo { @@ -33,7 +34,6 @@ public void execute(String generatorName, String library, boolean isEmbedded, bo options.put("interfaceOnly", TRUE); options.put("useTags", TRUE); options.put("useBeanValidation", TRUE); - options.put("useClassLevelBeanValidation", FALSE); options.put("useOptional", FALSE); options.put("useJakartaEe", TRUE); options.put("useSpringBoot3", TRUE); @@ -64,7 +64,21 @@ public void execute(String generatorName, String library, boolean isEmbedded, bo var merged = new HashMap<>(); merged.putAll(defaultOptions); merged.putAll(overrides); + boolean sb3 = propertyToBool(merged.get(SpringCodegen.USE_SPRING_BOOT3)); + boolean sb4 = propertyToBool(merged.get(SpringCodegen.USE_SPRING_BOOT4)); + if (sb3 && sb4) { + merged.put(SpringCodegen.USE_SPRING_BOOT3, FALSE); + } return merged; } + + private static boolean propertyToBool(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } + return false; + } } diff --git a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateMojo.java b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateMojo.java index 73ee45dc6..1329003c6 100644 --- a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateMojo.java +++ b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateMojo.java @@ -15,7 +15,7 @@ import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyLanguageSpecificPrimitivesCsv; import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyLanguageSpecificPrimitivesCsvList; import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyNameMappingsKvpList; -import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyOpenAPINormalizerKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyOpenapiNormalizerKvpList; import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyReservedWordsMappingsKvp; import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyReservedWordsMappingsKvpList; import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applySchemaMappingsKvp; @@ -880,7 +880,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { } if (openapiNormalizer != null && (configOptions == null || !configOptions.containsKey("openapi-normalizer"))) { - applyOpenAPINormalizerKvpList(openapiNormalizer, configurator); + applyOpenapiNormalizerKvpList(openapiNormalizer, configurator); } // Apply Schema Mappings diff --git a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateRestTemplateEmbeddedMojo.java b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateRestTemplateEmbeddedMojo.java index d4b2d414e..d3e5bdb54 100644 --- a/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateRestTemplateEmbeddedMojo.java +++ b/boat-maven-plugin/src/main/java/com/backbase/oss/boat/GenerateRestTemplateEmbeddedMojo.java @@ -20,6 +20,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { @Override protected Collection getGeneratorSpecificSupportingFiles() { - return Set.of("BigDecimalCustomSerializer.java"); + return Set.of("BigDecimalCustomSerializer.java", "BaseApi.java"); } } diff --git a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java index 664959683..9db91bb40 100644 --- a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java +++ b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java @@ -94,7 +94,7 @@ void useJavaBoatForRestTemplateEmbedded() throws MojoExecutionException, MojoFai ); assertThat( mojo.getGeneratorSpecificSupportingFiles(), - containsInAnyOrder("BigDecimalCustomSerializer.java") + containsInAnyOrder("BigDecimalCustomSerializer.java", "BaseApi.java") ); } @@ -146,7 +146,6 @@ void shouldApplyDefaultConfigOptionsForSpringBoot() throws MojoExecutionExceptio expectedOpts.put("interfaceOnly", "true"); expectedOpts.put("useTags", "true"); expectedOpts.put("useBeanValidation", "true"); - expectedOpts.put("useClassLevelBeanValidation", "false"); expectedOpts.put("useOptional", "false"); expectedOpts.put("useJakartaEe", "true"); expectedOpts.put("useSpringBoot3", "true"); @@ -177,7 +176,6 @@ void shouldOverrideDefaultConfigOptionsForSpringBoot() throws MojoExecutionExcep expectedOpts.put("interfaceOnly", "true"); expectedOpts.put("useTags", "true"); expectedOpts.put("useBeanValidation", "true"); - expectedOpts.put("useClassLevelBeanValidation", "false"); expectedOpts.put("useOptional", "true"); expectedOpts.put("useJakartaEe", "true"); expectedOpts.put("useSpringBoot3", "true"); diff --git a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/radio/RadioMojoTests.java b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/radio/RadioMojoTests.java index 2b84379b0..374c8d924 100644 --- a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/radio/RadioMojoTests.java +++ b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/radio/RadioMojoTests.java @@ -588,9 +588,10 @@ private List buildSampleBoatLintReports(String expectedDefaultKe boatLintReport.setId(BigDecimal.TEN); boatLintReport.setGrade(reportGrade); boatLintReport.violations( - List.of(BoatViolation.builder().severity(sampleSeverityInResponse).build())); + List.of(new BoatViolation().severity(sampleSeverityInResponse))); boatLintReport.setSpec( - BoatSpec.builder().key(expectedDefaultKey).description(description).changes(typeOfChange).build()); + new BoatSpec().key(expectedDefaultKey).description(description).changes(typeOfChange)); + return List.of(boatLintReport); } diff --git a/boat-scaffold/README.md b/boat-scaffold/README.md index 6738ccc1b..c34096e0b 100644 --- a/boat-scaffold/README.md +++ b/boat-scaffold/README.md @@ -9,20 +9,13 @@ The `boat` plugin has multiple goals: |-|-|-| | `addBindingResult` | `false` | Adds BindingResult to Api method definitions' request bodies if UseBeanValidation true, for this to be effective you must configure UseBeanValidation, this is not done automatically | | `addServletRequest` | `false` | Adds ServletRequest objects to API method definitions | -| `useClassLevelBeanValidation` | `false` | Adds @Validated annotation to API interfaces | -| `useLombokAnnotations` | `false` | Use Lombok annotations to generate properties accessors and `hashCode`/`equals`/`toString` methods | | `useSetForUniqueItems` | `false` | Use `java.util.Set` for arrays that has the attribute `uniqueItems` to `true` | | `openApiNullable` | `true` | Whether to use the `jackson-databind-nullable` library | -| `useWithModifiers` | `false` | Generates bean `with` modifiers for fluent style | -| `useProtectedFields` | `false` | Whether to use protected visibility for model fields | ## Java Code Generator | Option | Default | Description | |-|-|-| -| `createApiComponent` | `true` | Whether to generate the client as a Spring component (`resttemplate` only) | | `restTemplateBeanName` | `none` | The qualifier of the `RestTemplate` used by the `ApiClient` (`resttemplate` only) | -| `useClassLevelBeanValidation` | `false` | Adds @Validated annotation to API interfaces | | `useJacksonConversion` | `false` | Use Jackson to convert query parameters (`resttemplate` only) | | `useSetForUniqueItems` | `false` | Use `java.util.Set` for arrays that has the attribute `uniqueItems` to `true` | -| `useProtectedFields` | `false` | "Whether to use protected visibility for model fields | diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java index b1fc08324..7a1b9eb3c 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java @@ -3,6 +3,7 @@ import com.backbase.oss.codegen.java.BoatCodeGenUtils.CodegenValueType; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; import lombok.Getter; import lombok.Setter; import org.openapitools.codegen.CliOption; @@ -12,38 +13,25 @@ import org.openapitools.codegen.utils.ModelUtils; import java.io.File; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.backbase.oss.codegen.java.BoatCodeGenUtils.getCollectionCodegenValue; public class BoatJavaCodeGen extends JavaClientCodegen { + private final Logger LOGGER = LoggerFactory.getLogger(BoatJavaCodeGen.class); + public static final String NAME = "boat-java"; - public static final String USE_WITH_MODIFIERS = "useWithModifiers"; - public static final String USE_CLASS_LEVEL_BEAN_VALIDATION = "useClassLevelBeanValidation"; public static final String USE_JACKSON_CONVERSION = "useJacksonConversion"; - public static final String USE_DEFAULT_API_CLIENT = "useDefaultApiClient"; public static final String REST_TEMPLATE_BEAN_NAME = "restTemplateBeanName"; - public static final String CREATE_API_COMPONENT = "createApiComponent"; - public static final String USE_PROTECTED_FIELDS = "useProtectedFields"; - @Setter - @Getter - protected boolean useWithModifiers; - @Setter - @Getter - protected boolean useClassLevelBeanValidation; @Setter @Getter protected boolean useJacksonConversion; @Getter @Setter - protected boolean useDefaultApiClient = true; - @Getter - @Setter protected String restTemplateBeanName; - @Getter - @Setter - protected boolean createApiComponent = true; public BoatJavaCodeGen() { this.useJakartaEe = true; @@ -51,20 +39,13 @@ public BoatJavaCodeGen() { this.embeddedTemplateDir = this.templateDir = NAME; - this.cliOptions.add(CliOption.newBoolean(USE_CLASS_LEVEL_BEAN_VALIDATION, - "Add @Validated to class-level Api interfaces", this.useClassLevelBeanValidation)); - this.cliOptions.add(CliOption.newBoolean(USE_WITH_MODIFIERS, - "Whether to use \"with\" prefix for POJO modifiers", this.useWithModifiers)); this.cliOptions.add(CliOption.newBoolean(USE_JACKSON_CONVERSION, "Whether to use Jackson to convert query parameters to String", this.useJacksonConversion)); - this.cliOptions.add(CliOption.newBoolean(USE_DEFAULT_API_CLIENT, - "Whether to use a default ApiClient with a builtin template", this.useDefaultApiClient)); this.cliOptions.add(CliOption.newString(REST_TEMPLATE_BEAN_NAME, "An optional RestTemplate bean name")); - this.cliOptions.add(CliOption.newString(CREATE_API_COMPONENT, - "Whether to generate the client as a Spring component")); - this.cliOptions.add(CliOption.newString(USE_PROTECTED_FIELDS, - "Whether to use protected visibility for model fields")); + + // change default to match creating @Component + this.setGenerateClientAsBean(true); } @Override @@ -76,19 +57,9 @@ public String getName() { public void processOpts() { super.processOpts(); - if (this.additionalProperties.containsKey(USE_WITH_MODIFIERS)) { - this.useWithModifiers = convertPropertyToBoolean(USE_WITH_MODIFIERS); - } - writePropertyBack(USE_WITH_MODIFIERS, this.useWithModifiers); - if (RESTTEMPLATE.equals(getLibrary())) { processRestTemplateOpts(); } - if (this.additionalProperties.containsKey(USE_PROTECTED_FIELDS)) { - this.additionalProperties.put("modelFieldsVisibility", "protected"); - } else { - this.additionalProperties.put("modelFieldsVisibility", "private"); - } if (!getLibrary().startsWith("jersey")) { this.supportingFiles.removeIf(f -> f.getTemplateFile().equals("ServerConfiguration.mustache")); @@ -98,11 +69,6 @@ public void processOpts() { } private void processRestTemplateOpts() { - if (this.additionalProperties.containsKey(USE_CLASS_LEVEL_BEAN_VALIDATION)) { - this.useClassLevelBeanValidation = convertPropertyToBoolean(USE_CLASS_LEVEL_BEAN_VALIDATION); - } - writePropertyBack(USE_CLASS_LEVEL_BEAN_VALIDATION, this.useClassLevelBeanValidation); - if (this.additionalProperties.containsKey(USE_JACKSON_CONVERSION)) { this.useJacksonConversion = convertPropertyToBoolean(USE_JACKSON_CONVERSION); } @@ -111,18 +77,8 @@ private void processRestTemplateOpts() { this.supportingFiles.removeIf(f -> f.getTemplateFile().equals("RFC3339DateFormat.mustache")); } - if (this.additionalProperties.containsKey(USE_DEFAULT_API_CLIENT)) { - this.useDefaultApiClient = convertPropertyToBoolean(USE_DEFAULT_API_CLIENT); - } - writePropertyBack(USE_DEFAULT_API_CLIENT, this.useDefaultApiClient); - this.restTemplateBeanName = (String) this.additionalProperties.get(REST_TEMPLATE_BEAN_NAME); - if (this.additionalProperties.containsKey(CREATE_API_COMPONENT)) { - this.createApiComponent = convertPropertyToBoolean(CREATE_API_COMPONENT); - } - writePropertyBack(CREATE_API_COMPONENT, this.createApiComponent); - if (useJacksonConversion) { final var serializerTemplate = "BigDecimalCustomSerializer"; String targetDir = (sourceFolder + File.separator + modelPackage).replace(".", java.io.File.separator); @@ -164,4 +120,15 @@ private boolean isArrayTypeOfEnum(Schema s) { return items.getEnum() != null; } + protected Schema getSchemaItems(ArraySchema schema) { + Schema items = schema.getItems(); + if (items == null) { + this.LOGGER.error("Undefined array inner type for `{}`. Default to String.", schema.getName()); + items = (new StringSchema()).description("TODO default missing array inner type to string"); + schema.setItems(items); + } + + return items; + } + } diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java index 89e23ee2e..daf247c08 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java @@ -46,12 +46,10 @@ public class BoatSpringCodeGen extends SpringCodegen { public static final String NAME = "boat-spring"; - public static final String USE_CLASS_LEVEL_BEAN_VALIDATION = "useClassLevelBeanValidation"; public static final String ADD_SERVLET_REQUEST = "addServletRequest"; public static final String ADD_BINDING_RESULT = "addBindingResult"; - public static final String USE_LOMBOK_ANNOTATIONS = "useLombokAnnotations"; - public static final String USE_WITH_MODIFIERS = "useWithModifiers"; - public static final String USE_PROTECTED_FIELDS = "useProtectedFields"; + + private static final String VENDOR_EXTENSION_NOT_NULL = "x-not-null"; static class NewLineIndent implements Mustache.Lambda { @@ -149,13 +147,6 @@ protected String postProcessLine(String line) { } } - /** - * Add @Validated to class-level Api interfaces. Defaults to false - */ - @Setter - @Getter - protected boolean useClassLevelBeanValidation; - /** * Adds a HttpServletRequest object to the API definition method. */ @@ -170,43 +161,16 @@ protected String postProcessLine(String line) { @Getter protected boolean addBindingResult; - /** - * Add Lombok to class-level Api models. Defaults to false - */ - @Setter - @Getter - protected boolean useLombokAnnotations; - - - /** - * Whether to use {@code with} prefix for pojos modifiers. - */ - @Setter - @Getter - protected boolean useWithModifiers; - - @Setter - @Getter - protected boolean useProtectedFields; - public BoatSpringCodeGen() { super(); this.embeddedTemplateDir = this.templateDir = NAME; this.openapiNormalizer.put("REF_AS_PARENT_IN_ALLOF", "true"); - this.cliOptions.add(CliOption.newBoolean(USE_CLASS_LEVEL_BEAN_VALIDATION, - "Add @Validated to class-level Api interfaces.", this.useClassLevelBeanValidation)); this.cliOptions.add(CliOption.newBoolean(ADD_SERVLET_REQUEST, "Adds a HttpServletRequest object to the API definition method.", this.addServletRequest)); this.cliOptions.add(CliOption.newBoolean(ADD_BINDING_RESULT, "Adds a Binding result as method perimeter. Only implemented if @validate is being used.", this.addBindingResult)); - this.cliOptions.add(CliOption.newBoolean(USE_LOMBOK_ANNOTATIONS, - "Add Lombok to class-level Api models. Defaults to false.", this.useLombokAnnotations)); - this.cliOptions.add(CliOption.newBoolean(USE_WITH_MODIFIERS, - "Whether to use \"with\" prefix for POJO modifiers.", this.useWithModifiers)); - this.cliOptions.add(CliOption.newString(USE_PROTECTED_FIELDS, - "Whether to use protected visibility for model fields")); this.apiNameSuffix = "Api"; } @@ -245,23 +209,29 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo * "overridden" to fix invalid code when the data type is a collection of a fully qualified classname. * eg. Set<@Valid com.backbase.dbs.arrangement.commons.model.TranslationItemDto> * - * @param codegenProperty + * @param itemsProperty * @param dataType * @return */ - String replaceBeanValidationCollectionType(CodegenProperty codegenProperty, String dataType) { - if (!useBeanValidation || isEmpty(dataType) || !codegenProperty.isModel || isResponseType(codegenProperty)) { + String replaceBeanValidationCollectionType(CodegenProperty itemsProperty, String dataType) { + if (!useBeanValidation || isEmpty(dataType) || isResponseType(itemsProperty)) { return dataType; } + String result = dataType; - if (!contains(dataType, "@Valid")) { - result = dataType.replace("<", "<@Valid "); - } - Matcher m = Pattern.compile("^(.+\\<)(@Valid) ([a-z\\.]+)([A-Z].*)(\\>)$").matcher(dataType); - if (m.matches()) { - // Set<@Valid com.backbase.dbs.arrangement.commons.model.TranslationItemDto> - result = m.group(1) + m.group(3) + m.group(2) + " " + m.group(4) + m.group(5); + if (itemsProperty.isModel) { + if (!contains(dataType, "@Valid")) { + result = dataType.replace("<", "<@Valid "); + } + Matcher m = Pattern.compile("^(.+\\<)(@Valid) ([a-z\\.]+)([A-Z].*)(\\>)$").matcher(dataType); + if (m.matches()) { + // Set<@Valid com.backbase.dbs.arrangement.commons.model.TranslationItemDto> + result = m.group(1) + m.group(3) + m.group(2) + " " + m.group(4) + m.group(5); + } + } else if (applyNotNullVendorExtension(itemsProperty, dataType)) { + result = dataType.replace("<", "<@NotNull "); } + return result; } @@ -280,6 +250,61 @@ private static boolean isResponseType(CodegenProperty codegenProperty) { return codegenProperty.baseName.toLowerCase(Locale.ROOT).contains("response"); } + private boolean applyNotNullVendorExtension(CodegenProperty itemsProperty, String dataType) { + if (contains(dataType, "@NotNull")) { + return false; + } + + return booleanExtension(itemsProperty, VENDOR_EXTENSION_NOT_NULL); + } + + private boolean booleanExtension(CodegenProperty itemsProperty, String name) { + if (itemsProperty == null || itemsProperty.getVendorExtensions() == null) { + return false; + } + + try { + return Boolean.parseBoolean(String.valueOf(itemsProperty.getVendorExtensions().getOrDefault(name, "false"))); + } catch (Exception e) { + return false; + } + } + + private boolean notNullVendorExtension(Schema containerSchema, Schema itemSchema) { + if (!useBeanValidation || itemSchema == null) { + return false; + } + + // Prefer explicit item-level extension + Boolean itemExt = readBooleanExtension(itemSchema, VENDOR_EXTENSION_NOT_NULL); + if (itemExt != null) { + return itemExt; + } + + // Optional fallback: allow container-level switch too + Boolean containerExt = readBooleanExtension(containerSchema, VENDOR_EXTENSION_NOT_NULL); + if (containerExt != null) { + return containerExt; + } + + return false; + } + + private Boolean readBooleanExtension(Schema schema, String name) { + if (schema == null || schema.getExtensions() == null) { + return null; + } + + Object value = schema.getExtensions().get(name); + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + return Boolean.parseBoolean(String.valueOf(value)); + } + return null; + } + @Override public String getName() { return NAME; @@ -324,35 +349,17 @@ public void processOpts() { serializerTemplate + ".java" )); this.importMapping.put(serializerTemplate, modelPackage + "." + serializerTemplate); + this.importMapping.put("JsonSerialize", "com.fasterxml.jackson.databind.annotation.JsonSerialize"); - if (this.additionalProperties.containsKey(USE_CLASS_LEVEL_BEAN_VALIDATION)) { - this.useClassLevelBeanValidation = convertPropertyToBoolean(USE_CLASS_LEVEL_BEAN_VALIDATION); - } if (this.additionalProperties.containsKey(ADD_SERVLET_REQUEST)) { this.addServletRequest = convertPropertyToBoolean(ADD_SERVLET_REQUEST); } if (this.additionalProperties.containsKey(ADD_BINDING_RESULT)) { this.addBindingResult = convertPropertyToBoolean(ADD_BINDING_RESULT); } - if (this.additionalProperties.containsKey(USE_LOMBOK_ANNOTATIONS)) { - this.useLombokAnnotations = convertPropertyToBoolean(USE_LOMBOK_ANNOTATIONS); - } - if (this.additionalProperties.containsKey(USE_WITH_MODIFIERS)) { - this.useWithModifiers = convertPropertyToBoolean(USE_WITH_MODIFIERS); - } - if (this.additionalProperties.containsKey(USE_PROTECTED_FIELDS)) { - this.additionalProperties.put("modelFieldsVisibility", "protected"); - } else { - this.additionalProperties.put("modelFieldsVisibility", "private"); - } - writePropertyBack(USE_CLASS_LEVEL_BEAN_VALIDATION, this.useClassLevelBeanValidation); writePropertyBack(ADD_SERVLET_REQUEST, this.addServletRequest); writePropertyBack(ADD_BINDING_RESULT, this.addBindingResult); - writePropertyBack(USE_LOMBOK_ANNOTATIONS, this.useLombokAnnotations); - writePropertyBack(USE_WITH_MODIFIERS, this.useWithModifiers); - writePropertyBack(USE_PROTECTED_FIELDS, this.useProtectedFields); - this.additionalProperties.put("indent4", new IndentedLambda(4, " ", true, true)); this.additionalProperties.put("newLine4", new NewLineIndent(4, " ")); diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodegenProperty.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodegenProperty.java index 60f0a750c..d4c35a19a 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodegenProperty.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodegenProperty.java @@ -45,7 +45,6 @@ public BoatSpringCodegenProperty(CodegenProperty codegenProperty) { super.exclusiveMaximum = codegenProperty.exclusiveMaximum; super.required = codegenProperty.required; super.deprecated = codegenProperty.deprecated; - super.hasMoreNonReadOnly = codegenProperty.hasMoreNonReadOnly; super.isPrimitiveType = codegenProperty.isPrimitiveType; super.isModel = codegenProperty.isModel; super.isContainer = codegenProperty.isContainer; @@ -100,6 +99,7 @@ public BoatSpringCodegenProperty(CodegenProperty codegenProperty) { super.nameInLowerCase = codegenProperty.nameInLowerCase; super.nameInCamelCase = codegenProperty.nameInCamelCase; super.nameInSnakeCase = codegenProperty.nameInSnakeCase; + super.nameInPascalCase = codegenProperty.nameInPascalCase; super.enumName = codegenProperty.enumName; super.maxItems = codegenProperty.maxItems; super.minItems = codegenProperty.minItems; diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatWebhooksCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatWebhooksCodeGen.java index 9a325fc72..a9bb938ef 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatWebhooksCodeGen.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatWebhooksCodeGen.java @@ -20,8 +20,6 @@ public class BoatWebhooksCodeGen extends BoatSpringCodeGen { public static final String ADD_SERVLET_REQUEST = "addServletRequest"; public static final String ADD_BINDING_RESULT = "addBindingResult"; public static final String USE_LOMBOK_ANNOTATIONS = "useLombokAnnotations"; - public static final String USE_WITH_MODIFIERS = "useWithModifiers"; - public static final String USE_PROTECTED_FIELDS = "useProtectedFields"; public static final String MUSTACHE_EXTENSION =".mustache"; public static final String JAVA_EXTENSION =".java"; @@ -32,19 +30,11 @@ public BoatWebhooksCodeGen() { this.embeddedTemplateDir = this.templateDir = NAME; this.openapiNormalizer.put("REF_AS_PARENT_IN_ALLOF", "true"); - this.cliOptions.add(CliOption.newBoolean(USE_CLASS_LEVEL_BEAN_VALIDATION, - "Add @Validated to class-level Api interfaces.", this.useClassLevelBeanValidation)); this.cliOptions.add(CliOption.newBoolean(ADD_SERVLET_REQUEST, "Adds a HttpServletRequest object to the API definition method.", this.addServletRequest)); this.cliOptions.add(CliOption.newBoolean(ADD_BINDING_RESULT, "Adds a Binding result as method perimeter. Only implemented if @validate is being used.", this.addBindingResult)); - this.cliOptions.add(CliOption.newBoolean(USE_LOMBOK_ANNOTATIONS, - "Add Lombok to class-level Api models. Defaults to false.", this.useLombokAnnotations)); - this.cliOptions.add(CliOption.newBoolean(USE_WITH_MODIFIERS, - "Whether to use \"with\" prefix for POJO modifiers.", this.useWithModifiers)); - this.cliOptions.add(CliOption.newString(USE_PROTECTED_FIELDS, - "Whether to use protected visibility for model fields")); supportedLibraries.put(NAME, "Boat Webhooks codegen"); this.apiNameSuffix = "Api"; this.apiNamePrefix = "Webhook"; diff --git a/boat-scaffold/src/main/templates/boat-docs/js_json_schema_ref_parser.mustache b/boat-scaffold/src/main/templates/boat-docs/js_json_schema_ref_parser.mustache index fb1bd144f..042c9b3c4 100644 --- a/boat-scaffold/src/main/templates/boat-docs/js_json_schema_ref_parser.mustache +++ b/boat-scaffold/src/main/templates/boat-docs/js_json_schema_ref_parser.mustache @@ -10663,7 +10663,7 @@ function crawl (obj, path, pathFromRoot, parents, $refs, options) { /** * Dereferences the given JSON Reference, and then crawls the resulting value. * - * @param {{$ref: string}} $ref - The JSON Reference to resolve + * @param $ref - The JSON Reference to resolve * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash * @param {string} pathFromRoot - The path of `$ref` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced @@ -12487,7 +12487,7 @@ function crawl (obj, path, $refs, options) { /** * Resolves the given JSON Reference, and then crawls the resulting value. * - * @param {{$ref: string}} $ref - The JSON Reference to resolve + * @param $ref - The JSON Reference to resolve * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash * @param {$Refs} $refs * @param {$RefParserOptions} options diff --git a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache index 09a7294b0..fedad6b56 100644 --- a/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache +++ b/boat-scaffold/src/main/templates/boat-java/libraries/resttemplate/ApiClient.mustache @@ -9,6 +9,9 @@ import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +{{#restTemplateBeanName}} +import org.springframework.beans.factory.annotation.Qualifier; +{{/restTemplateBeanName}} import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -130,7 +133,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { {{#generateClientAsBean}} @Autowired {{/generateClientAsBean}} - public ApiClient(RestTemplate restTemplate) { + public ApiClient({{#restTemplateBeanName}}@Qualifier("{{restTemplateBeanName}}") {{/restTemplateBeanName}} RestTemplate restTemplate) { this.restTemplate = restTemplate; init(); } diff --git a/boat-scaffold/src/main/templates/boat-spring/api.mustache b/boat-scaffold/src/main/templates/boat-spring/api.mustache index 7f396c340..9f3ac9721 100644 --- a/boat-scaffold/src/main/templates/boat-spring/api.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/api.mustache @@ -64,6 +64,12 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.http.codec.multipart.Part; {{/reactive}} +{{#addServletRequest}} +import {{javaxPackage}}.servlet.http.HttpServletRequest; +{{/addServletRequest}} +{{#addBindingResult}} +import org.springframework.validation.BindingResult; +{{/addBindingResult}} {{#useBeanValidation}} import {{javaxPackage}}.validation.Valid; @@ -269,7 +275,7 @@ public interface {{classname}} { @ResponseBody {{/vendorExtensions.x-sse}} {{#jdk8-default-interface}}default {{/jdk8-default-interface}}{{>responseType}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}( - {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, + {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{>httpServletParam}}{{^-last}}, {{/-last}}{{/allParams}}{{#reactive}}{{#hasParams}}, {{/hasParams}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true){{/swagger2AnnotationLibrary}}{{#springFoxDocumentationProvider}}@ApiIgnore{{/springFoxDocumentationProvider}} final ServerWebExchange exchange{{/reactive}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#reactive}},{{/reactive}}{{/hasParams}}{{#springFoxDocumentationProvider}}@ApiIgnore {{/springFoxDocumentationProvider}}{{#springDocDocumentationProvider}}@ParameterObject {{/springDocDocumentationProvider}}final Pageable pageable{{/vendorExtensions.x-spring-paginated}}{{#vendorExtensions.x-spring-provide-args}}{{#hasParams}}, diff --git a/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache b/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache index 0aed4a92d..cc10e7f3d 100644 --- a/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/beanValidation.mustache @@ -1 +1 @@ -{{#required}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/required}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}@Valid {{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}@Valid {{/isPrimitiveType}}{{/isContainer}}{{^openApiNullable}}{{>beanValidationCore}}{{/openApiNullable}}{{#openApiNullable}}{{^useOptional}}{{>beanValidationCore}}{{/useOptional}}{{/openApiNullable}}{{#useOptional}}{{#openApiNullable}}{{#isContainer}}{{^required}}{{>beanValidationCore}}{{/required}}{{/isContainer}}{{/openApiNullable}}{{#openApiNullable}}{{#required}}{{>beanValidationCore}}{{/required}}{{/openApiNullable}}{{/useOptional}} \ No newline at end of file +{{#required}}{{^isReadOnly}}{{^isNullable}}@NotNull{{/isNullable}}{{/isReadOnly}}{{/required}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}@Valid{{/isPrimitiveType}}{{/isContainer}}{{^openApiNullable}}{{>beanValidationCore}}{{/openApiNullable}}{{#openApiNullable}}{{^useOptional}}{{>beanValidationCore}}{{/useOptional}}{{/openApiNullable}}{{#useOptional}}{{#openApiNullable}}{{#isContainer}}{{^required}}{{>beanValidationCore}}{{/required}}{{/isContainer}}{{/openApiNullable}}{{#openApiNullable}}{{#required}}{{>beanValidationCore}}{{/required}}{{/openApiNullable}}{{/useOptional}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache b/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache index ea2019e65..5c9f59ded 100644 --- a/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/bodyParams.mustache @@ -1 +1 @@ -{{#isBodyParam}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{>beanValidationBodyParams}}{{/useBeanValidation}} @RequestBody{{^required}}(required = false){{/required}} {{^reactive}}{{>nullableAnnotation}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isBodyParam}} \ No newline at end of file +{{#isBodyParam}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{>beanValidationBodyParams}}{{/useBeanValidation}} @RequestBody{{^required}}(required = false){{/required}} {{^reactive}}{{>nullableAnnotation}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}Mono<{{{dataType}}}>{{/isArray}}{{#isArray}}Flux<{{{baseType}}}>{{/isArray}}{{/reactive}} {{paramName}}{{#useBeanValidation}}{{#addBindingResult}}, BindingResult bindingResult{{/addBindingResult}}{{/useBeanValidation}}{{/isBodyParam}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/mapDataType.mustache b/boat-scaffold/src/main/templates/boat-spring/mapDataType.mustache index 5f7a2196b..612ca0432 100644 --- a/boat-scaffold/src/main/templates/boat-spring/mapDataType.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/mapDataType.mustache @@ -1,21 +1,2 @@ -{{#openApiNullable}} - {{#isNullable}}JsonNullable<{{/isNullable}} -{{/openApiNullable}} {{! apply map type with String as key type }} -{{{baseType}}}beanValidationCore}} - {{/isPrimitiveType}} - {{^isPrimitiveType}} - @Valid - {{/isPrimitiveType}} - {{/items}} -{{/useBeanValidation}} -{{! apply map value type with closing bracket }} -{{{items.datatypeWithEnum}}}> -{{#openApiNullable}} - {{#isNullable}}>{{/isNullable}} -{{/openApiNullable}} \ No newline at end of file +{{{baseType}}}beanValidationCore}}{{/isPrimitiveType}}{{^isPrimitiveType}}@Valid{{/isPrimitiveType}}{{/items}}{{/useBeanValidation}} {{{items.datatypeWithEnum}}}> \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache b/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache index de4ee36cf..5e5007368 100644 --- a/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/nullableAnnotation.mustache @@ -1 +1 @@ -{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/defaultValue}}{{/required}} \ No newline at end of file +{{! deprecated feature since Spring 7.0 }} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache b/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache index 8d6fc6dec..b2aeb3992 100644 --- a/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache +++ b/boat-scaffold/src/main/templates/boat-spring/nullableDataTypeBeanValidation.mustache @@ -1 +1 @@ -{{#openApiNullable}}{{#isNullable}}{{^isContainer}}JsonNullable<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{#isContainer}}JsonNullable<{{/isContainer}}{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}}{{{datatypeWithEnum}}}{{#openApiNullable}}{{#isNullable}}>{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}>{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}} \ No newline at end of file +{{#openApiNullable}}{{#isNullable}}{{^isContainer}}JsonNullable<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{#isContainer}}JsonNullable<{{/isContainer}}{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}Optional<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}}{{#isMap}}{{>mapDataType}}{{/isMap}}{{^isMap}}{{{datatypeWithEnum}}}{{/isMap}}{{#openApiNullable}}{{#isNullable}}>{{/isNullable}}{{#useOptional}}{{^required}}{{^isNullable}}{{^isContainer}}>{{/isContainer}}{{/isNullable}}{{/required}}{{/useOptional}}{{/openApiNullable}} \ No newline at end of file diff --git a/boat-scaffold/src/main/templates/boat-webhooks/api.mustache b/boat-scaffold/src/main/templates/boat-webhooks/api.mustache index 8c7583158..effc5256b 100644 --- a/boat-scaffold/src/main/templates/boat-webhooks/api.mustache +++ b/boat-scaffold/src/main/templates/boat-webhooks/api.mustache @@ -53,6 +53,9 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.http.codec.multipart.Part; {{/reactive}} +{{#addServletRequest}} +import {{javaxPackage}}.servlet.http.HttpServletRequest; +{{/addServletRequest}} {{#useBeanValidation}} {{#useJakartaEe}} diff --git a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatJavaCodeGenTests.java b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatJavaCodeGenTests.java index 1dd8c6215..5d9e49e20 100644 --- a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatJavaCodeGenTests.java +++ b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatJavaCodeGenTests.java @@ -1,18 +1,13 @@ package com.backbase.oss.codegen.java; -import static com.backbase.oss.codegen.java.BoatJavaCodeGen.CREATE_API_COMPONENT; import static com.backbase.oss.codegen.java.BoatJavaCodeGen.REST_TEMPLATE_BEAN_NAME; -import static com.backbase.oss.codegen.java.BoatJavaCodeGen.USE_CLASS_LEVEL_BEAN_VALIDATION; -import static com.backbase.oss.codegen.java.BoatJavaCodeGen.USE_DEFAULT_API_CLIENT; import static com.backbase.oss.codegen.java.BoatJavaCodeGen.USE_JACKSON_CONVERSION; -import static com.backbase.oss.codegen.java.BoatJavaCodeGen.USE_PROTECTED_FIELDS; -import static com.backbase.oss.codegen.java.BoatJavaCodeGen.USE_WITH_MODIFIERS; import static java.util.stream.Collectors.groupingBy; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.openapitools.codegen.languages.JavaClientCodegen.GENERATE_CLIENT_AS_BEAN; import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; @@ -52,13 +47,8 @@ void processOptsWithRestTemplateDefaults() { gen.setLibrary("resttemplate"); gen.processOpts(); - assertThat(gen.useWithModifiers, is(false)); - assertThat(gen.useClassLevelBeanValidation, is(false)); - assertThat(gen.useJacksonConversion, is(false)); - assertThat(gen.useDefaultApiClient, is(true)); assertThat(gen.restTemplateBeanName, is(nullValue())); - assertThat(gen.createApiComponent, is(true)); } @Test @@ -67,23 +57,14 @@ void processOptsWithRestTemplate() { final Map options = gen.additionalProperties(); gen.setLibrary("resttemplate"); - options.put(USE_WITH_MODIFIERS, "true"); - options.put(USE_CLASS_LEVEL_BEAN_VALIDATION, "true"); options.put(USE_JACKSON_CONVERSION, "true"); - options.put(USE_DEFAULT_API_CLIENT, "false"); options.put(REST_TEMPLATE_BEAN_NAME, "the-coolest-rest-template-in-this-universe"); - options.put(CREATE_API_COMPONENT, "false"); gen.processOpts(); - assertThat(gen.useWithModifiers, is(true)); - assertThat(gen.useClassLevelBeanValidation, is(true)); - assertThat(gen.useJacksonConversion, is(true)); - assertThat(gen.useDefaultApiClient, is(false)); assertThat(gen.restTemplateBeanName, is("the-coolest-rest-template-in-this-universe")); - assertThat(gen.createApiComponent, is(false)); } @Test @@ -91,35 +72,15 @@ void processOptsWithoutRestTemplate() { final BoatJavaCodeGen gen = new BoatJavaCodeGen(); final Map options = gen.additionalProperties(); - options.put(USE_WITH_MODIFIERS, "true"); - options.put(USE_CLASS_LEVEL_BEAN_VALIDATION, "true"); - options.put(USE_JACKSON_CONVERSION, "true"); - options.put(USE_DEFAULT_API_CLIENT, "false"); options.put(REST_TEMPLATE_BEAN_NAME, "the-coolest-rest-template-in-this-universe"); gen.processOpts(); - assertThat(gen.useWithModifiers, is(true)); - assertThat(gen.useClassLevelBeanValidation, is(false)); - assertThat(gen.useJacksonConversion, is(false)); - assertThat(gen.useDefaultApiClient, is(true)); assertThat(gen.restTemplateBeanName, is(nullValue())); } - @Test - void processOptsUseProtectedFields() { - final BoatJavaCodeGen gen = new BoatJavaCodeGen(); - final Map options = gen.additionalProperties(); - - options.put(USE_PROTECTED_FIELDS, "true"); - - gen.processOpts(); - - assertThat(gen.additionalProperties(), hasEntry("modelFieldsVisibility", "protected")); - } - @ParameterizedTest @ValueSource(booleans = {true, false}) void shouldHonourGenerateComponentAnnotation(boolean generate) throws FileNotFoundException { @@ -137,7 +98,7 @@ void shouldHonourGenerateComponentAnnotation(boolean generate) throws FileNotFou final Map options = gen.additionalProperties(); options.put("library", "resttemplate"); - options.put(CREATE_API_COMPONENT, String.valueOf(generate)); + options.put(GENERATE_CLIENT_AS_BEAN, String.valueOf(generate)); var openApiInput = new OpenAPIParser() .readLocation(input.getAbsolutePath(), null, new ParseOptions()) @@ -163,7 +124,6 @@ void shouldHonourGenerateComponentAnnotation(boolean generate) throws FileNotFou .findFirst(TypeDeclaration.class).get(); assertThat(apiClientType.getAnnotationByName("Component").isPresent(), is(generate)); - assertThat(gen.createApiComponent, is(generate)); assertThat(gen.getLibrary(), is("resttemplate")); } diff --git a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java index 5d9abb58c..c0c52fb20 100644 --- a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java +++ b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java @@ -1,10 +1,8 @@ package com.backbase.oss.codegen.java; -import static com.backbase.oss.codegen.java.BoatSpringCodeGen.USE_PROTECTED_FIELDS; import static java.util.stream.Collectors.groupingBy; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isA; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -86,20 +84,6 @@ void clientOptsUnicity() { .forEach((k, v) -> assertEquals(1, v.size(), k + " is described multiple times")); } - - @Test - void processOptsUseProtectedFields() { - final BoatJavaCodeGen gen = new BoatJavaCodeGen(); - final Map options = gen.additionalProperties(); - - options.put(USE_PROTECTED_FIELDS, "true"); - - gen.processOpts(); - - assertThat(gen.additionalProperties(), hasEntry("modelFieldsVisibility", "protected")); - } - - @Test void newLineIndent() throws IOException { final NewLineIndent indent = new BoatSpringCodeGen.NewLineIndent(2, "_"); @@ -189,11 +173,9 @@ void shouldGenerateValidations(boolean useLombok, boolean bigDecimalsAsStrings) codegen.setInputSpec(input.getAbsolutePath()); codegen.setContainerDefaultToNull(true); codegen.setSerializeBigDecimalAsString(bigDecimalsAsStrings); - codegen.setUseLombokAnnotations(useLombok); codegen.schemaMapping().put("ValidatedPojo", REFERENCED_CLASS_NAME); codegen.schemaMapping().put("CommonEnum", REFERENCED_ENUM_NAME); codegen.additionalProperties().put(SpringCodegen.USE_SPRING_BOOT3, Boolean.TRUE.toString()); - codegen.additionalProperties().put(BoatSpringCodeGen.USE_CLASS_LEVEL_BEAN_VALIDATION, Boolean.TRUE.toString()); codegen.setModelPackage(modelPackage); var openApiInput = new OpenAPIParser() @@ -244,24 +226,24 @@ void shouldGenerateValidations(boolean useLombok, boolean bigDecimalsAsStrings) .findFirst() .get(); CompilationUnit paymentRequestUnit = StaticJavaParser.parse(paymentRequest); - assertFieldAnnotation(paymentRequestUnit, "currencyCode", "Pattern"); - assertFieldAnnotation(paymentRequestUnit, "currencyCode", "NotNull"); - assertFieldAnnotation(paymentRequestUnit, "referenceNumber", "Size"); - assertFieldAnnotation(paymentRequestUnit, "referenceNumber", "NotNull"); - assertFieldAnnotation(paymentRequestUnit, "requestLine", "Valid"); + assertFieldGetterAnnotation(paymentRequestUnit, "currencyCode", "Pattern"); + assertFieldGetterAnnotation(paymentRequestUnit, "currencyCode", "NotNull"); + assertFieldGetterAnnotation(paymentRequestUnit, "referenceNumber", "Size"); + assertFieldGetterAnnotation(paymentRequestUnit, "referenceNumber", "NotNull"); + assertFieldGetterAnnotation(paymentRequestUnit, "requestLine", "Valid"); File multiLinePaymentRequest = files.stream().filter(f -> f.getName().equals("MultiLinePaymentRequest.java")) .findFirst() .get(); CompilationUnit multiLinePaymentRequestUnit = StaticJavaParser.parse(multiLinePaymentRequest); - assertFieldAnnotation(multiLinePaymentRequestUnit, "arrangementIds", "NotNull"); + assertFieldGetterAnnotation(multiLinePaymentRequestUnit, "arrangementIds", "NotNull"); assertFieldValueAssignment( - multiLinePaymentRequestUnit, "arrangementIds", "new ArrayList<>()"); - assertFieldAnnotation(multiLinePaymentRequestUnit, "uniqueLines", "NotNull"); - assertFieldAnnotation(multiLinePaymentRequestUnit, "name", "Pattern", "@Pattern(regexp = \"^[^\\\\r\\\\n]{1,64}$\")"); + multiLinePaymentRequestUnit, "arrangementIds", "new ArrayList<>()"); + assertFieldGetterAnnotation(multiLinePaymentRequestUnit, "uniqueLines", "NotNull"); + assertFieldGetterAnnotation(multiLinePaymentRequestUnit, "name", "Pattern", "@Pattern(regexp = \"^[^\\\\r\\\\n]{1,64}$\")"); assertFieldValueAssignment( - multiLinePaymentRequestUnit, "uniqueArrangementIds", null); + multiLinePaymentRequestUnit, "uniqueArrangementIds", null); // assert annotation @@ -461,6 +443,24 @@ private static void assertFieldAnnotation( assertThat(annotation.toString(), equalTo(value)); } + private static void assertFieldGetterAnnotation( + CompilationUnit unit, String fieldName, String annotationName) throws FileNotFoundException { + String getterName = "get" + fieldName; + MethodDeclaration methodDeclaration = findMethodDeclaration(unit, getterName); + assertThat("Expect annotation to be present on field: " + annotationName + " " + fieldName, + methodDeclaration.getAnnotationByName(annotationName).isPresent(), is(true)); + } + + private static void assertFieldGetterAnnotation( + CompilationUnit unit, String fieldName, String annotationName, String value) throws FileNotFoundException { + String getterName = "get" + fieldName; + MethodDeclaration methodDeclaration = findMethodDeclaration(unit, getterName); + AnnotationExpr annotation = methodDeclaration.getAnnotationByName(annotationName) + .orElseThrow(() -> new AssertionError( + "Expect annotation to be present on method: " + annotationName + " " + getterName)); + assertThat(annotation.toString(), equalTo(value)); + } + private static void assertFieldValueAssignment( CompilationUnit unit, String fieldName, String valueAssignment) throws FileNotFoundException { FieldDeclaration fieldDeclaration = findFieldDeclaration(unit, fieldName); @@ -491,6 +491,17 @@ private static FieldDeclaration findFieldDeclaration(CompilationUnit unit, Strin return result.get(); } + private static MethodDeclaration findMethodDeclaration(CompilationUnit unit, String methodName) { + Optional result = unit + .findAll(MethodDeclaration.class) + .stream() + .filter(m -> m.getName().getIdentifier().equalsIgnoreCase(methodName)) + .findFirst(); + assertThat("Expect method declaration to be present: " + methodName, + result.isPresent(), is(true)); + return result.get(); + } + @NotNull private static Object newPaymeyntRequestLineObject(String modelPackage, ClassLoader projectClassLoader, String id) throws Exception { diff --git a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java index eec51db26..0198968d5 100644 --- a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java +++ b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java @@ -1,10 +1,7 @@ package com.backbase.oss.codegen.java; -import static java.util.Arrays.asList; import static java.util.Arrays.stream; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static java.util.stream.Stream.concat; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -34,11 +31,9 @@ import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import java.util.regex.Pattern; -import java.util.stream.IntStream; import java.util.stream.Stream; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -46,7 +41,6 @@ import org.apache.commons.lang.UnhandledException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.openapitools.codegen.ClientOptInput; import org.openapitools.codegen.CodegenConstants; @@ -86,68 +80,111 @@ static public void setUpClass() throws IOException { } static class Combination { - static final List CASES = asList("flx", "val", "opt", "req", "bin", "lmb", "wth", "utl"); - - final String name; - - final boolean useBeanValidation; - final boolean useOptional; - - final boolean addServletRequest; - final boolean addBindingResult; - final boolean useLombokAnnotations; - final boolean useWithModifiers; - - final boolean reactive; - final boolean apiUtil; - - Combination(int mask) { - this.name = mask == 0 - ? "boat" - : IntStream.range(0, CASES.size()) - .filter(n -> (mask & (1 << n)) != 0) - .mapToObj(CASES::get) - .collect(joining("-", "boat-", "")); - - this.useBeanValidation = (mask & 1 << CASES.indexOf("val")) != 0; - this.addBindingResult = (mask & 1 << CASES.indexOf("bin")) != 0; - this.useOptional = (mask & 1 << CASES.indexOf("opt")) != 0; - this.addServletRequest = (mask & 1 << CASES.indexOf("req")) != 0; - this.useLombokAnnotations = (mask & 1 << CASES.indexOf("lmb")) != 0; - this.useWithModifiers = (mask & 1 << CASES.indexOf("wth")) != 0; - this.reactive = (mask & 1 << CASES.indexOf("flx")) != 0; - this.apiUtil = (mask & 1 << CASES.indexOf("utl")) != 0; - } - static Stream combinations(boolean minimal) { - final List cases = new ArrayList<>(); + String name = "boat"; - if (minimal) { - cases.add(0); - } + boolean useBeanValidation = false; + boolean useOptional = false; - // generate all combinations - // TODO find a better way to keep only the relevant combinations - for (int mask = 0; mask < 1 << CASES.size(); mask++) { - if (minimal && Integer.bitCount(mask) != 1) { - continue; - } + boolean addServletRequest = false; + boolean addBindingResult = false; - cases.add(mask); - } + boolean reactive = false; + boolean apiUtil = false; + + private Combination() {} - if (minimal) { - cases.add(~(1 << CASES.indexOf("flx"))); - //everything except flx & utl (because req & flx together is incorrect - cases.add(-514); - //everything except req - cases.add(~(1 << CASES.indexOf("req"))); + private Combination(boolean all) { + if (all) { + this.addBindingResult = true; + this.addServletRequest = true; + this.apiUtil = true; + this.reactive = true; + this.useBeanValidation = true; + this.useOptional = true; + this.name = "boat-val-opt-bin-utl-servlet-reactive"; } + } + + public static Combination create() { + return new Combination(); + } + + public static Combination all() { + return new Combination(true); + } + + public Combination validation() { + this.useBeanValidation = true; + this.name += "-validation"; + return this; + } - return cases.stream().map(Combination::new); + public Combination optional() { + this.useOptional = true; + this.name += "-optional"; + return this; } + + public Combination servlet() { + this.addServletRequest = true; + this.name += "-servlet"; + return this; + } + + public Combination binding() { + this.addBindingResult = true; + this.name += "-binding"; + return this; + } + + public Combination reactive() { + this.reactive = true; + this.name += "-reactive"; + return this; + } + + public Combination apiUtil() { + this.apiUtil = true; + this.name += "-util"; + return this; + } + + public Combination noReactive() { + this.reactive = false; + this.name.replace("-reactive", ""); + return this; + } + + public Combination noServlet() { + this.addServletRequest = false; + this.name.replace("-servlet", ""); + return this; + } + + @Override + public String toString() { + return "Combination(" + + name + + ')'; + } + } + + static Stream cases() { + return Stream.of( + Combination.create(), + Combination.create().validation(), + Combination.create().optional(), + Combination.create().servlet(), + Combination.create().binding(), + Combination.create().reactive(), + Combination.create().apiUtil(), + Combination.all().noReactive(), + Combination.all().noServlet() + ); } + /** dynamic suite creation **/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -156,109 +193,102 @@ static Stream combinations(boolean minimal) { @TestFactory Stream withCombinations() { - return Combination - .combinations(PROP_FAST) - .map(param -> dynamicContainer(param.name, testStream(param))); + return cases() + .map(param -> { + + List files = generateFrom(null, param); + + return dynamicContainer( + param.name, + findCheckMethods().map( + m -> dynamicTest(m.getName(), () -> invoke(m, param, files)))); + }); } - Stream testStream(Combination param) { - return concat( - Stream.of(dynamicTest("generate", () -> generate(param))), - stream(getClass().getDeclaredMethods()) - .filter(m -> m.isAnnotationPresent(Check.class)) - .map(m -> dynamicTest(m.getName(), () -> invoke(m)))); + Stream findCheckMethods() { + return stream(this.getClass().getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(Check.class)) + .filter(m -> { + Class[] params = m.getParameterTypes(); + if (params.length != 2 || + !params[0].equals(Combination.class) || + !params[1].equals(List.class)) { + + throw new IllegalStateException( + "@Check method must include (Combination param, List files) parameters: " + m + ); + } + return true; + }); } @SneakyThrows - private void invoke(Method m) { - m.invoke(this); + private void invoke(Method m, Combination param, List files) { + m.invoke(this, param, files); } /** the actual testing code **/ - private Combination param; - private List files; - - void generate(Combination param) { - this.param = param; - this.files = generateFrom(null, param.name); - // used in development - // this.files = generateFrom("openapi-generator-originals/JavaSpring-4.3.1"); - - assertThat(this.files, not(nullValue())); - assertThat(this.files.size(), not(equalTo(0))); - } - @Check - void useBeanValidation() { - assertThat(findPattern("/api/.+\\.java$", "@Valid"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/model/.+\\.java$", "@Valid"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/model/MultiLinePaymentRequest.*\\.java$", "List<@Pattern\\(regexp"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/model/MultiLinePaymentRequest.*\\.java$", "Map files) { + assertThat(files, not(nullValue())); + assertThat(files.size(), not(equalTo(0))); } @Check - void queryParamsCustomNotNullValidation() { - assertThat(findPattern("/api/ArrayTypesApi\\.java$", "List<@NotNull.*>\\s+qParamsNotNull"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/api/SetTypesApi\\.java$", "Set\\s*<@NotNull.*>\\s+qParamsNotNull"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/api/SimpleTypesApi\\.java$", "Set\\s*<@NotNull.*>\\s+qParamsNotNull"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); - assertThat(findPattern("/api/MapTypesApi\\.java$", "List<@NotNull.*>\\s+qParamsNotNull"), - equalTo(this.param.useBeanValidation || this.param.addBindingResult)); + void useBeanValidation(Combination param, List files) { + assertThat(findPattern(files, "/api/.+\\.java$", "@Valid"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files, "/model/.+\\.java$", "@Valid"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files, "/model/MultiLinePaymentRequest.*\\.java$", "List<@Pattern\\(regexp"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files, "/model/MultiLinePaymentRequest.*\\.java$", "Map]+>"), - equalTo(this.param.useOptional)); - assertThat(findPattern("/model/.+\\.java$", "Optional<[^>]+>"), - is(false)); + void queryParamsCustomNotNullValidation(Combination param, List files) { + assertThat(findPattern(files, "/api/ArrayTypesApi\\.java$", "List<@NotNull.*>\\s+qParamsNotNull"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files, "/api/SetTypesApi\\.java$", "Set\\s*<@NotNull.*>\\s+qParamsNotNull"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files, "/api/SimpleTypesApi\\.java$", "Set\\s*<@NotNull.*>\\s+qParamsNotNull"), + equalTo(param.useBeanValidation || param.addBindingResult)); + assertThat(findPattern(files,"/api/MapTypesApi\\.java$", "List<@NotNull.*>\\s+qParamsNotNull"), + equalTo(param.useBeanValidation || param.addBindingResult)); } @Check - void addServletRequest() { - assertThat(findPattern("/api/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), - equalTo(this.param.addServletRequest)); - assertThat(findPattern("/model/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), + void useOptional(Combination param, List files) { + assertThat(findPattern(files, "/api/.+\\.java$", "Optional<(?!NativeWebRequest)[^>]+>"), + equalTo(param.useOptional)); + assertThat(findPattern(files, "/model/.+\\.java$", "Optional<[^>]+>"), is(false)); } @Check - void addBindingResult(){ - assertThat(findPattern("/api/.+\\.java$", "BindingResult\\s+bindingResult"), - equalTo(this.param.addBindingResult)); - assertThat(findPattern("/model/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), - is(false)); - } - - @Check - void useLombokAnnotations() { - assertThat(findPattern("/api/.+\\.java$", "@lombok\\.Getter"), + void addServletRequest(Combination param, List files) { + assertThat(findPattern(files, "/api/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), + equalTo(param.addServletRequest)); + assertThat(findPattern(files, "/model/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), is(false)); - assertThat(findPattern("/model/.+\\.java$", "@lombok\\.Getter"), - equalTo(this.param.useLombokAnnotations)); } @Check - void useWithModifiers() { - assertThat(findPattern("/api/.+\\.java$", "\\s+with\\p{Upper}"), + void addBindingResult(Combination param, List files){ + assertThat(findPattern(files, "/api/.+\\.java$", "BindingResult\\s+bindingResult"), + equalTo(param.addBindingResult)); + assertThat(findPattern(files, "/model/.+\\.java$", "HttpServletRequest\\s+httpServletRequest"), is(false)); - assertThat(findPattern("/model/.+\\.java$", "\\s+with\\p{Upper}"), - equalTo(this.param.useWithModifiers)); } @Check - void checkCompiles() throws Exception { + void checkCompiles(Combination param, List files) throws Exception { final var projectDir = new File(TEST_OUTPUT, param.name); assertThat(projectDir + " is not a directory", projectDir.isDirectory()); compileGeneratedProject(projectDir); - verifyGeneratedClasses(projectDir); + verifyGeneratedClasses(param.name, projectDir); } private static void compileGeneratedProject(File projectDir) { @@ -266,15 +296,15 @@ private static void compileGeneratedProject(File projectDir) { assertEquals(0, compilationStatus, "Could not compile generated project in dir: " + projectDir); } - private void verifyGeneratedClasses(File projectDir) throws Exception { + private void verifyGeneratedClasses(String paramName, File projectDir) throws Exception { var classLoader = MAVEN_PROJECT_COMPILER.getProjectClassLoader(projectDir); - verifyReceivableRequestModelJsonConversion(classLoader); - verifyMultiLineRequest(classLoader); + verifyReceivableRequestModelJsonConversion(paramName, classLoader); + verifyMultiLineRequest(paramName, classLoader); } - private void verifyReceivableRequestModelJsonConversion(ClassLoader classLoader) throws InterruptedException { - String testedModelClassName = buildReceivableRequestModelClassName(); - String parentModelClassName = buildPaymentRequestModelClassName(); + private void verifyReceivableRequestModelJsonConversion(String paramName, ClassLoader classLoader) throws InterruptedException { + String testedModelClassName = buildReceivableRequestModelClassName(paramName); + String parentModelClassName = buildPaymentRequestModelClassName(paramName); var objectMapper = new ObjectMapper(); Runnable verificationRunnable = () -> { try { @@ -316,7 +346,7 @@ private void verifyReceivableRequestModelJsonConversion(ClassLoader classLoader) }; var verificationRunner = new VerificationRunner(classLoader); verificationRunner.runVerification( - Verification.builder().runnable(verificationRunnable).displayName(param.name).build() + Verification.builder().runnable(verificationRunnable).displayName(paramName).build() ); } @@ -329,8 +359,8 @@ private void verifyJsonIgnoreAnnotation(Class modelClass) { } } - private void verifyMultiLineRequest(ClassLoader classLoader) throws InterruptedException { - String testedModelClassName = buildMultiLineRequestModelClassName(); + private void verifyMultiLineRequest(String paramName, ClassLoader classLoader) throws InterruptedException { + String testedModelClassName = buildMultiLineRequestModelClassName(paramName); Runnable verificationRunnable = () -> { try { Class modelClass = classLoader.loadClass(testedModelClassName); @@ -344,42 +374,42 @@ private void verifyMultiLineRequest(ClassLoader classLoader) throws InterruptedE }; var verificationRunner = new VerificationRunner(classLoader); verificationRunner.runVerification( - Verification.builder().runnable(verificationRunnable).displayName(param.name).build() + Verification.builder().runnable(verificationRunnable).displayName(paramName).build() ); } /** * Build proper class name for `ReceivableRequest`. */ - private String buildReceivableRequestModelClassName() { - return buildModelClassName("ReceivableRequest"); + private String buildReceivableRequestModelClassName(String paramName) { + return buildModelClassName(paramName, "ReceivableRequest"); } /** * Build proper class name for `PaymentRequest` (parent/discriminator base). */ - private String buildPaymentRequestModelClassName() { - return buildModelClassName("PaymentRequest"); + private String buildPaymentRequestModelClassName(String paramName) { + return buildModelClassName(paramName, "PaymentRequest"); } - private String buildModelClassName(String baseName) { - var modelPackage = param.name.replace('-', '.') + ".model"; + private String buildModelClassName(String paramName, String baseName) { + var modelPackage = paramName.replace('-', '.') + ".model"; var classNameSuffix = org.apache.commons.lang3.StringUtils.capitalize( - param.name.indexOf('-') > -1 - ? CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, param.name) - : param.name + paramName.indexOf('-') > -1 + ? CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, paramName) + : paramName ); return modelPackage + "." + baseName + classNameSuffix; } - private String buildMultiLineRequestModelClassName() { - return buildModelClassName("MultiLinePaymentRequest"); + private String buildMultiLineRequestModelClassName(String paramName) { + return buildModelClassName(paramName, "MultiLinePaymentRequest"); } - private boolean findPattern(String filePattern, String linePattern) { + private boolean findPattern(List files, String filePattern, String linePattern) { final Predicate fileMatch = Pattern.compile(filePattern).asPredicate(); log.info("Files: {}", files); - final List selection = this.files.stream() + final List selection = files.stream() .map(File::getPath) .map(path -> path.replace(File.separatorChar, '/')) .filter(fileMatch) @@ -399,13 +429,13 @@ private boolean contentMatches(String path, Predicate lineMatch) { } } - private List generateFrom(String templates, String combination) { + private List generateFrom(String templates, Combination param) { final File input = new File("src/test/resources/boat-spring/openapi.yaml"); final CodegenConfigurator gcf = new CodegenConfigurator(); gcf.setGeneratorName(BoatSpringCodeGen.NAME); gcf.setInputSpec(input.getAbsolutePath()); - gcf.setOutputDir(TEST_OUTPUT + "/" + combination); + gcf.setOutputDir(TEST_OUTPUT + "/" + param.name); GlobalSettings.setProperty(CodegenConstants.APIS, ""); GlobalSettings.setProperty(CodegenConstants.API_DOCS, "true"); @@ -418,25 +448,22 @@ private List generateFrom(String templates, String combination) { gcf.setApiNameSuffix("-api"); - gcf.setModelNameSuffix(this.param.name); + gcf.setModelNameSuffix(param.name); - gcf.addAdditionalProperty(OptionalFeatures.USE_OPTIONAL, this.param.useOptional); + gcf.addAdditionalProperty(OptionalFeatures.USE_OPTIONAL, param.useOptional); - gcf.addAdditionalProperty(BoatSpringCodeGen.USE_CLASS_LEVEL_BEAN_VALIDATION, true); - gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_SERVLET_REQUEST, this.param.addServletRequest); - gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_BINDING_RESULT,this.param.addBindingResult); - if (this.param.addBindingResult) { + gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_SERVLET_REQUEST, param.addServletRequest); + gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_BINDING_RESULT,param.addBindingResult); + if (param.addBindingResult) { gcf.addAdditionalProperty(BeanValidationFeatures.USE_BEANVALIDATION, true); } else { - gcf.addAdditionalProperty(BeanValidationFeatures.USE_BEANVALIDATION, this.param.useBeanValidation); + gcf.addAdditionalProperty(BeanValidationFeatures.USE_BEANVALIDATION, param.useBeanValidation); } - gcf.addAdditionalProperty(BoatSpringCodeGen.USE_LOMBOK_ANNOTATIONS, this.param.useLombokAnnotations); gcf.addAdditionalProperty(BoatSpringCodeGen.OPENAPI_NULLABLE, false); - gcf.addAdditionalProperty(BoatSpringCodeGen.USE_WITH_MODIFIERS, this.param.useWithModifiers); - gcf.addAdditionalProperty(SpringCodegen.REACTIVE, this.param.reactive); + gcf.addAdditionalProperty(SpringCodegen.REACTIVE, param.reactive); - final String destPackage = this.param.name.replace('-', '.') + "."; + final String destPackage = param.name.replace('-', '.') + "."; gcf.setApiPackage(destPackage + "api"); gcf.setModelPackage(destPackage + "model"); @@ -445,6 +472,7 @@ private List generateFrom(String templates, String combination) { gcf.addAdditionalProperty(SpringCodegen.BASE_PACKAGE, destPackage + "base"); gcf.addAdditionalProperty(SpringCodegen.CONFIG_PACKAGE, destPackage + "config"); + gcf.addAdditionalProperty(SpringCodegen.USE_SPRING_BOOT3, true); gcf.addAdditionalProperty(CodegenConstants.HIDE_GENERATION_TIMESTAMP, true); gcf.addAdditionalProperty(SpringCodegen.INTERFACE_ONLY, false); gcf.addAdditionalProperty(SpringCodegen.USE_TAGS, true); @@ -454,21 +482,28 @@ private List generateFrom(String templates, String combination) { + " \n" + " jakarta.persistence\n" + " jakarta.persistence-api\n" - + " 2.2.3\n" + + " 3.1.0\n" + " \n" + " \n" + " jakarta.servlet\n" + " jakarta.servlet-api\n" - + " 4.0.4\n" + + " 6.0.0\n" + " \n" + " \n" + " org.springframework.boot\n" + " spring-boot-starter-webflux\n" + " \n" + + " org.springframework.boot\n" + + " spring-boot-starter-json\n" + + " \n" + + " \n" + + " com.fasterxml.jackson.core\n" + + " jackson-databind\n" + + " \n" + " \n" + " org.openapitools\n" + " jackson-databind-nullable\n" - + " 0.2.1\n" + + " 0.2.9\n" + " \n" + ""); diff --git a/boat-scaffold/src/test/resources/oas-examples/petstore-example-refs.yaml b/boat-scaffold/src/test/resources/oas-examples/petstore-example-refs.yaml index 729730b98..bd8e585d4 100644 --- a/boat-scaffold/src/test/resources/oas-examples/petstore-example-refs.yaml +++ b/boat-scaffold/src/test/resources/oas-examples/petstore-example-refs.yaml @@ -147,7 +147,7 @@ paths: InternalServerError: $ref: "#/components/examples/BadRequestError" OtherError: - $ref: "#/components/examples/InvalidReference" + $ref: "#/components/examples/BadRequestError" default: description: unexpected error content: From 9841dbf54599a2de7d0c93ef92df4eec197238ed Mon Sep 17 00:00:00 2001 From: marcinm_backbase <63855724+bbzurek@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:40:06 +0100 Subject: [PATCH 3/5] fix build --- boat-maven-plugin/pom.xml | 12 ++++++++++++ boat-scaffold/pom.xml | 1 + 2 files changed, 13 insertions(+) diff --git a/boat-maven-plugin/pom.xml b/boat-maven-plugin/pom.xml index 80caf12ad..3c8e8aae0 100644 --- a/boat-maven-plugin/pom.xml +++ b/boat-maven-plugin/pom.xml @@ -53,6 +53,18 @@ org.openapitools.openapidiff openapi-diff-core 2.1.7 + + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + diff --git a/boat-scaffold/pom.xml b/boat-scaffold/pom.xml index e40ef908f..479e70c16 100644 --- a/boat-scaffold/pom.xml +++ b/boat-scaffold/pom.xml @@ -161,6 +161,7 @@ org.apache.maven.resolver maven-resolver-transport-http ${maven.resolver.version} + test com.github.javaparser From e8909d73df43d44eae5623958aee2b086389f76d Mon Sep 17 00:00:00 2001 From: marcinm_backbase <63855724+bbzurek@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:54:20 +0100 Subject: [PATCH 4/5] sonar issues --- .../backbase/oss/boat/GenerateMojoTests.java | 30 ++++++++++++++++ .../oss/codegen/java/BoatSpringCodeGen.java | 35 ------------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java index 9db91bb40..c282cdb6a 100644 --- a/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java +++ b/boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java @@ -187,6 +187,36 @@ void shouldOverrideDefaultConfigOptionsForSpringBoot() throws MojoExecutionExcep }); } + @Test + void shouldResolveSpringBootOptions() throws MojoExecutionException, MojoFailureException { + GenerateMojo mojo = configure(new GenerateSpringBootEmbeddedMojo(), null); + // add overrides + mojo.configOptions = Map.of( + "useSpringBoot4", "true" + ); + + mojo.execute(); + + Map expectedOpts = new HashMap<>(); + expectedOpts.put("java8", "true"); + expectedOpts.put("dateLibrary", "java8"); + expectedOpts.put("performBeanValidation", "true"); + expectedOpts.put("skipDefaultInterface", "true"); + expectedOpts.put("interfaceOnly", "true"); + expectedOpts.put("useTags", "true"); + expectedOpts.put("useBeanValidation", "true"); + expectedOpts.put("useOptional", "false"); + expectedOpts.put("useJakartaEe", "true"); + expectedOpts.put("useSpringBoot3", "false"); + expectedOpts.put("useSpringBoot4", "true"); + expectedOpts.put("containerDefaultToNull", "false"); + + assertNotNull(mojo.configOptions); + expectedOpts.forEach((key, value) -> { + assertEquals(value, mojo.configOptions.get(key)); + }); + } + private T configure(T mojo, String generatorName) { mojo.buildContext = buildContext; mojo.project = project; diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java index daf247c08..633651516 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java @@ -270,41 +270,6 @@ private boolean booleanExtension(CodegenProperty itemsProperty, String name) { } } - private boolean notNullVendorExtension(Schema containerSchema, Schema itemSchema) { - if (!useBeanValidation || itemSchema == null) { - return false; - } - - // Prefer explicit item-level extension - Boolean itemExt = readBooleanExtension(itemSchema, VENDOR_EXTENSION_NOT_NULL); - if (itemExt != null) { - return itemExt; - } - - // Optional fallback: allow container-level switch too - Boolean containerExt = readBooleanExtension(containerSchema, VENDOR_EXTENSION_NOT_NULL); - if (containerExt != null) { - return containerExt; - } - - return false; - } - - private Boolean readBooleanExtension(Schema schema, String name) { - if (schema == null || schema.getExtensions() == null) { - return null; - } - - Object value = schema.getExtensions().get(name); - if (value instanceof Boolean) { - return (Boolean) value; - } - if (value instanceof String) { - return Boolean.parseBoolean(String.valueOf(value)); - } - return null; - } - @Override public String getName() { return NAME; From 6d9a47a6f856d24331fccce2fdf90dc2839c2df1 Mon Sep 17 00:00:00 2001 From: marcinm_backbase <63855724+bbzurek@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:42:43 +0100 Subject: [PATCH 5/5] sonar issues --- .../oss/codegen/java/BoatJavaCodeGen.java | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java index 7a1b9eb3c..15949b96b 100644 --- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java +++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatJavaCodeGen.java @@ -1,9 +1,7 @@ package com.backbase.oss.codegen.java; import com.backbase.oss.codegen.java.BoatCodeGenUtils.CodegenValueType; -import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; import lombok.Getter; import lombok.Setter; import org.openapitools.codegen.CliOption; @@ -13,15 +11,11 @@ import org.openapitools.codegen.utils.ModelUtils; import java.io.File; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static com.backbase.oss.codegen.java.BoatCodeGenUtils.getCollectionCodegenValue; public class BoatJavaCodeGen extends JavaClientCodegen { - private final Logger LOGGER = LoggerFactory.getLogger(BoatJavaCodeGen.class); - public static final String NAME = "boat-java"; public static final String USE_JACKSON_CONVERSION = "useJacksonConversion"; @@ -113,22 +107,10 @@ private boolean isArrayTypeOfEnum(Schema s) { if (!ModelUtils.isArraySchema(target)) { return false; } - Schema items = getSchemaItems((ArraySchema) target); + Schema items = ModelUtils.getSchemaItems(target); if (items.get$ref() != null) { items = openAPI.getComponents().getSchemas().get(ModelUtils.getSimpleRef(items.get$ref())); } return items.getEnum() != null; } - - protected Schema getSchemaItems(ArraySchema schema) { - Schema items = schema.getItems(); - if (items == null) { - this.LOGGER.error("Undefined array inner type for `{}`. Default to String.", schema.getName()); - items = (new StringSchema()).description("TODO default missing array inner type to string"); - schema.setItems(items); - } - - return items; - } - }

Response Details
Status Code Description Response Headers
{{code}} {{message}} {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}}