From e4c7ccc3714c1efa1b028a0aebfa00c56353dda6 Mon Sep 17 00:00:00 2001 From: Thibault Pensec <39826516+symphony-thibault@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:31:29 +0100 Subject: [PATCH] Extension mechanism with Groups Management APIs (#620) The BDK extension mechanism aims to allow developers to provide additional features without necessary having to contribute in the finos/symphony-bdk-java repository. All extensions must implement the BdkExtension marker interface, and can optionally implement extension point interfaces such as: - `BdkExtensionServiceProvider` - `BdkApiClientFactoryAware` - `BdkAuthenticationAware` - `BdkRetryBuilderAware` - `BdkConfigurationAware` As a first use-case, this PR also brings the Symphony's Groups API support as an extension. --- build.gradle | 2 +- buildSrc/build.gradle | 5 + .../bdk.java-codegen-conventions.gradle | 37 ++++ docs/extension.md | 181 ++++++++++++++++++ docs/index.md | 1 + settings.gradle | 12 ++ symphony-bdk-bom/build.gradle | 4 + symphony-bdk-config/README.md | 4 + symphony-bdk-config/build.gradle | 27 +++ .../bdk/core/config/BdkConfigLoader.java | 0 .../bdk/core/config/BdkConfigParser.java | 0 .../config/exception/BdkConfigException.java | 0 .../exception/BdkConfigFormatException.java | 16 ++ .../exception/BotNotConfiguredException.java | 0 .../core/config/extension/BdkConfigAware.java | 20 ++ .../config/legacy/LegacyConfigMapper.java | 0 .../model/LegacyRetryConfiguration.java | 0 .../config/legacy/model/LegacySymConfig.java | 0 .../bdk/core/config/model/BdkAgentConfig.java | 0 .../config/model/BdkAuthenticationConfig.java | 2 +- .../bdk/core/config/model/BdkBotConfig.java | 0 .../config/model/BdkCertificateConfig.java | 7 +- .../core/config/model/BdkClientConfig.java | 0 .../core/config/model/BdkCommonJwtConfig.java | 0 .../bdk/core/config/model/BdkConfig.java | 0 .../core/config/model/BdkDatafeedConfig.java | 0 .../core/config/model/BdkExtAppConfig.java | 0 .../config/model/BdkLoadBalancingConfig.java | 0 .../config/model/BdkLoadBalancingMode.java | 0 .../bdk/core/config/model/BdkProxyConfig.java | 0 .../bdk/core/config/model/BdkRetryConfig.java | 0 .../core/config/model/BdkRsaKeyConfig.java | 0 .../core/config/model/BdkServerConfig.java | 0 .../bdk/core/config/model/BdkSslConfig.java | 6 +- .../core/config/util/DeprecationLogger.java | 28 +++ .../bdk/core/config/BdkConfigLoaderTest.java | 0 .../bdk/core/config/BdkConfigParserTest.java | 0 .../bdk/core/config/BdkConfigTest.java | 8 +- .../config/util/DeprecationLoggerTest.java | 54 ++++++ .../certs/all_symphony_certs_truststore | Bin 0 -> 4542 bytes .../src/test/resources/certs/extapp-cert.p12 | Bin 0 -> 3525 bytes .../src/test/resources/certs/identity.p12 | Bin 0 -> 4226 bytes .../src/test/resources/config/config.json | 0 .../src/test/resources/config/config.yaml | 25 +++ .../config/config_client_global.yaml | 0 .../config_flat_client_global.properties | 0 .../src/test/resources/config/config_lb.yaml | 0 .../resources/config/config_lb_external.yaml | 0 .../config/config_lb_invalid_mode.yaml | 0 .../config/config_lb_no_stickiness.yaml | 0 .../config/config_lb_properties.yaml | 0 .../config/config_lb_round_robin.yaml | 0 .../resources/config/config_properties.json | 0 .../resources/config/config_properties.yaml | 0 .../config/config_recursive_props.json | 0 .../resources/config/df_retry_config.yaml | 0 .../test/resources/config/invalid_config.html | 0 .../test/resources/config/invalid_config.yaml | 0 .../test/resources/config/legacy_config.json | 0 .../test/resources/config/no_bot_config.yaml | 15 ++ symphony-bdk-core/build.gradle | 8 +- .../com/symphony/bdk/core/SymphonyBdk.java | 17 +- .../symphony/bdk/core/SymphonyBdkBuilder.java | 20 +- .../core/auth/impl/AuthenticationRetry.java | 2 +- .../bdk/core/auth/impl/OAuthentication.java | 3 +- .../bdk/core/client/ApiClientFactory.java | 43 ++++- .../ApiClientInitializationException.java | 3 - .../extension/BdkApiClientFactoryAware.java | 21 ++ .../extension/BdkAuthenticationAware.java | 22 +++ .../core/extension/BdkRetryBuilderAware.java | 22 +++ .../bdk/core/extension/ExtensionService.java | 141 ++++++++++++++ .../exception/BdkExtensionException.java | 11 ++ .../bdk/core/retry/RecoveryStrategy.java | 2 +- .../bdk/core/retry/RetryWithRecovery.java | 2 +- .../core/retry/RetryWithRecoveryBuilder.java | 4 +- .../function/ConsumerWithThrowable.java | 4 +- .../function/SupplierWithApiException.java | 4 +- .../Resilience4jRetryWithRecovery.java | 4 +- .../util/BdkExponentialFunction.java | 2 +- .../application/ApplicationService.java | 2 +- .../service/connection/ConnectionService.java | 2 +- .../service/disclaimer/DisclaimerService.java | 2 +- .../core/service/health/HealthService.java | 2 +- .../core/service/message/MessageService.java | 2 +- .../service/presence/PresenceService.java | 2 +- .../core/service/session/SessionService.java | 2 +- .../core/service/signal/SignalService.java | 2 +- .../core/service/stream/StreamService.java | 2 +- .../bdk/core/service/user/UserService.java | 2 +- .../symphony/bdk/core/util/UserIdUtil.java | 84 ++++++++ .../auth/impl/AuthenticationRetryTest.java | 2 +- .../bdk/core/client/ApiClientFactoryTest.java | 18 +- .../core/extension/ExtensionServiceTest.java | 92 +++++++++ .../TestExtensionApiClientFactoryAware.java | 31 +++ .../TestExtensionAuthenticationAware.java | 31 +++ .../extension/TestExtensionConfigAware.java | 31 +++ .../TestExtensionRetryBuilderAware.java | 31 +++ .../extension/TestExtensionWithService.java | 16 ++ ...estExtensionWithoutDefaultConstructor.java | 8 + .../Resilience4jRetryWithRecoveryTest.java | 4 +- .../core/util/BdkExponentialFunctionTest.java | 1 + .../bdk/core/util/UserIdUtilTest.java | 24 +++ .../bdk-group-example/build.gradle | 20 ++ .../bdk/examples/GroupExtensionExample.java | 82 ++++++++ .../src/main/resources/groups.ftl | 15 ++ .../bdk-spring-boot-example/build.gradle | 4 + .../spring/api/ApiExceptionHandler.java | 30 +++ .../bdk/examples/spring/api/GroupApi.java | 36 ++++ .../spring/config/GroupExtensionConfig.java | 22 +++ symphony-bdk-extension-api/build.gradle | 12 ++ .../symphony/bdk/extension/BdkExtension.java | 14 ++ .../bdk/extension/BdkExtensionService.java | 10 + .../BdkExtensionServiceProvider.java | 23 +++ symphony-bdk-extensions/build.gradle | 3 + .../symphony-group-extension/README.md | 63 ++++++ .../symphony-group-extension/build.gradle | 39 ++++ .../ext/group/SymphonyGroupBdkExtension.java | 55 ++++++ .../bdk/ext/group/SymphonyGroupService.java | 110 +++++++++++ .../symphony/bdk/ext/group/auth/OAuth.java | 21 ++ .../bdk/ext/group/auth/OAuthSession.java | 82 ++++++++ .../bdk/http/api/auth/Authentication.java | 4 +- .../spring/SymphonyBdkAutoConfiguration.java | 6 +- .../bdk/spring/config/BdkExtensionConfig.java | 70 +++++++ .../bdk/spring/config/BdkRetryConfig.java | 18 ++ .../SymphonyBdkAutoConfigurationTest.java | 10 + .../bdk/spring/extension/TestExtension.java | 18 ++ .../extension/TestExtensionService.java | 6 + 127 files changed, 1882 insertions(+), 73 deletions(-) create mode 100644 buildSrc/src/main/groovy/bdk.java-codegen-conventions.gradle create mode 100644 docs/extension.md create mode 100644 symphony-bdk-config/README.md create mode 100644 symphony-bdk-config/build.gradle rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/BdkConfigLoader.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/BdkConfigParser.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigException.java (100%) create mode 100644 symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigFormatException.java rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/exception/BotNotConfiguredException.java (100%) create mode 100644 symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/extension/BdkConfigAware.java rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/legacy/LegacyConfigMapper.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacyRetryConfiguration.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacySymConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkAgentConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java (97%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkBotConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java (91%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkClientConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkCommonJwtConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkDatafeedConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkExtAppConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingMode.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkProxyConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkRetryConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkRsaKeyConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkServerConfig.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java (89%) create mode 100644 symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/util/DeprecationLogger.java rename {symphony-bdk-core => symphony-bdk-config}/src/test/java/com/symphony/bdk/core/config/BdkConfigLoaderTest.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/java/com/symphony/bdk/core/config/BdkConfigParserTest.java (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java (92%) create mode 100644 symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/util/DeprecationLoggerTest.java create mode 100644 symphony-bdk-config/src/test/resources/certs/all_symphony_certs_truststore create mode 100644 symphony-bdk-config/src/test/resources/certs/extapp-cert.p12 create mode 100644 symphony-bdk-config/src/test/resources/certs/identity.p12 rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config.json (100%) create mode 100644 symphony-bdk-config/src/test/resources/config/config.yaml rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_client_global.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_flat_client_global.properties (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb_external.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb_invalid_mode.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb_no_stickiness.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb_properties.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_lb_round_robin.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_properties.json (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_properties.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/config_recursive_props.json (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/df_retry_config.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/invalid_config.html (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/invalid_config.yaml (100%) rename {symphony-bdk-core => symphony-bdk-config}/src/test/resources/config/legacy_config.json (100%) create mode 100644 symphony-bdk-config/src/test/resources/config/no_bot_config.yaml create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkApiClientFactoryAware.java create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkAuthenticationAware.java create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkRetryBuilderAware.java create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/ExtensionService.java create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/exception/BdkExtensionException.java rename symphony-bdk-core/src/main/java/com/symphony/bdk/core/{util => retry}/function/ConsumerWithThrowable.java (87%) rename symphony-bdk-core/src/main/java/com/symphony/bdk/core/{util => retry}/function/SupplierWithApiException.java (88%) rename symphony-bdk-core/src/main/java/com/symphony/bdk/core/{ => retry}/util/BdkExponentialFunction.java (97%) create mode 100644 symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/UserIdUtil.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/ExtensionServiceTest.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionApiClientFactoryAware.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionAuthenticationAware.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionConfigAware.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionRetryBuilderAware.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithService.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithoutDefaultConstructor.java create mode 100644 symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/UserIdUtilTest.java create mode 100644 symphony-bdk-examples/bdk-group-example/build.gradle create mode 100644 symphony-bdk-examples/bdk-group-example/src/main/java/com/symphony/bdk/examples/GroupExtensionExample.java create mode 100644 symphony-bdk-examples/bdk-group-example/src/main/resources/groups.ftl create mode 100644 symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/ApiExceptionHandler.java create mode 100644 symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/GroupApi.java create mode 100644 symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/config/GroupExtensionConfig.java create mode 100644 symphony-bdk-extension-api/build.gradle create mode 100644 symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtension.java create mode 100644 symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionService.java create mode 100644 symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionServiceProvider.java create mode 100644 symphony-bdk-extensions/build.gradle create mode 100644 symphony-bdk-extensions/symphony-group-extension/README.md create mode 100644 symphony-bdk-extensions/symphony-group-extension/build.gradle create mode 100644 symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupBdkExtension.java create mode 100644 symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupService.java create mode 100644 symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuth.java create mode 100644 symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuthSession.java create mode 100644 symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkExtensionConfig.java create mode 100644 symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkRetryConfig.java create mode 100644 symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtension.java create mode 100644 symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtensionService.java diff --git a/build.gradle b/build.gradle index 541c7ae22..2d237c1c3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id "io.codearte.nexus-staging" version "0.22.0" } -ext.projectVersion = '2.5.0-SNAPSHOT' +ext.projectVersion = '2.6.0-SNAPSHOT' ext.isReleaseVersion = !ext.projectVersion.endsWith('SNAPSHOT') ext.mavenRepoUrl = project.properties['mavenRepoUrl'] ?: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 4fa76d065..6b042e955 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -5,3 +5,8 @@ plugins { repositories { gradlePluginPortal() } + +dependencies { + implementation 'org.openapitools:openapi-generator-gradle-plugin:5.3.1' + implementation 'de.undercouch:gradle-download-task:4.1.2' +} diff --git a/buildSrc/src/main/groovy/bdk.java-codegen-conventions.gradle b/buildSrc/src/main/groovy/bdk.java-codegen-conventions.gradle new file mode 100644 index 000000000..e71e9842f --- /dev/null +++ b/buildSrc/src/main/groovy/bdk.java-codegen-conventions.gradle @@ -0,0 +1,37 @@ +plugins { + id 'org.openapi.generator' +} + +dependencies { + implementation 'javax.annotation:jsr250-api:1.0' + implementation 'io.swagger:swagger-annotations' + implementation 'com.google.code.findbugs:jsr305' +} + + +def generatedFolder = "$buildDir/generated/openapi" +sourceSets.main.java.srcDirs += "$generatedFolder/src/main/java" + +tasks.compileJava.dependsOn tasks.openApiGenerate + +openApiGenerate { + generatorName = 'java' + library = 'jersey2' + outputDir = generatedFolder + inputSpec = "$projectDir/src/main/resources/api.yaml" + skipOverwrite = true + generateApiTests = false + generateModelTests = false + generateModelDocumentation = false + generateApiDocumentation = false + invokerPackage = 'com.symphony.bdk.http.api' + templateDir = "${rootDir}/templates" + globalProperties = [ + models : "", + apis : "", + supportingFiles: "false" + ] + configOptions = [ + dateLibrary: "java8" + ] +} diff --git a/docs/extension.md b/docs/extension.md new file mode 100644 index 000000000..c7113485e --- /dev/null +++ b/docs/extension.md @@ -0,0 +1,181 @@ +# Extension Model +> :bulb: since `2.6` + +> :warning: The BDK Extension Mechanism is still an experimental feature, contracts might be subject to **breaking changes** +> in following versions. + +## Overview +The BDK extension model consists of a simple concept: the `BdkExtension` API. Note, however, that `BdkExtension` +itself is just a marker interface. + +The `BdkExtension` API is available through the module `:symphony-bdk-extension-api` but other modules might be required +depending on what your extension needs to use. + +## Registering Extensions +Extensions are registered _programmatically_ via the `ExtensionService`: +```java +class ExtensionExample { + + public static void main(String[] args) { + // using the ExtensionService + final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml")); + bdk.extensions().register(MyBdkExtension.class); + + // or using the SymphonyBdkBuilder + final SymphonyBdk bdk = SymphonyBdk.builder() + .config(loadFromSymphonyDir("config.yaml")) + .extension(MyBdkExtension.class) + .build(); + } +} +``` + +### Registering Extensions in Spring Boot +To use your extension in the [BDK Spring Boot Starter](./spring-boot/core-starter.md), you simply need to register your +extension as a bean added to the application context. Note that your extension class must implement `BdkExtension` in order +to automatically be registered: +```java +@Configuration +public class MyBdkExtensionConfig { + + @Bean + public MyBdkExtension myBdkExtension() { + return new MyBdkExtension(); + } +} +``` +This way, your extension will automatically be registered within the `ExtensionService`. + +## Service Provider Extension +A _Service Provider_ extension is a specific type of extension loaded on demand when calling the +`ExtensionService#service(Class)` method. + +To make your extension _Service Provider_, your extension definition class must implement the `BdkExtensionServiceProvider` +interface along with the `BdkExtension` marker interface: +```java +/** + * The Service implementation class. + */ +public class MyBdkExtensionService implements BdkExtensionService { + + public void sayHello(String name) { + System.out.println("Hello, %s!", name); // #noLog4Shell + } +} +/** + * The Extension definition class. + */ +public class MyBdkExtension implements BdkExtension, BdkExtensionServiceProvider { + + private final MyBdkExtensionService service = new MyBdkExtensionService(); + + @Override + public MyBdkExtensionService getService() { + return this.service; + } +} +/** + * Usage example. + */ +class ExtensionExample { + + public static void main(String[] args) { + final SymphonyBdk bdk = SymphonyBdk.builder() + .config(loadFromSymphonyDir("config.yaml")) + .extension(MyBdkExtension.class) + .build(); + + final MyBdkExtensionService service = bdk.extensions().service(MyBdkExtension.class); + service.sayHello("Symphony"); + } +} +``` + +### Access your Extension's service in Spring Boot +In Spring Boot, your extension's service is _lazily_ initialized. It means that you must annotate your injected extension's service +field with the `@Lazy` annotation in addition to the `@Autowired` one: +```java +@Configuration +public class MyBdkExtensionConfig { + + @Bean + public MyBdkExtension myBdkExtension() { + return new MyBdkExtension(); + } +} + +@RestController +@RequestMapping("/api") +public class ApiController { + + @Lazy // required, otherwise Spring Boot application startup will fail + @Autowired + private MyBdkExtensionService groupService; +} +``` +> :bulb: Note that your IDE might show an error like "_Could not autowire. No beans of 'MyBdkExtensionService' type found_". +> To disable this warning you can annotate your class with `@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")` + +## BDK Aware Extensions +The BDK Extension Model allows extensions to access to some core objects such as the configuration or the api clients. +Developers that wish to use these objects a free to implement a set of interfaces all suffixed with the `Aware` keyword. + +### `BdkConfigAware` +The interface `com.symphony.bdk.core.config.extension.BdkConfigAware` allows extensions to read the BDK configuration: +```java +public class MyBdkExtension implements BdkExtension, BdkConfigAware { + + private BdkConfig config; + + @Override + public void setConfiguration(BdkConfig config) { + this.config = config; + } +} +``` + +### `BdkApiClientFactoryAware` +The interface `com.symphony.bdk.core.extension.BdkApiClientFactoryAware` can be used by extensions that need to +use the `com.symphony.bdk.core.client.ApiClientFactory` class: +```java +public class MyBdkExtension implements BdkExtension, BdkApiClientFactoryAware { + + private ApiClientFactory apiClientFactory; + + @Override + public void setApiClientFactory(ApiClientFactory apiClientFactory) { + this.apiClientFactory = apiClientFactory; + } +} +``` + +### `BdkAuthenticationAware` +The interface `com.symphony.bdk.core.extension.BdkAuthenticationAware` can be used by extensions that need to rely on the +service account authentication session (`com.symphony.bdk.core.auth.AuthSession`), which provides the `sessionToken` and +`keyManagerToken` that are used to call the Symphony's APIs: +```java +public class MyBdkExtension implements BdkExtension, BdkAuthenticationAware { + + private AuthSession authSession; + + @Override + public void setAuthSession(AuthSession authSession) { + this.authSession = authSession; + } +} +``` + +### `BdkRetryBuilderAware` +The interface `com.symphony.bdk.core.extension.BdkRetryBuilderAware` allows extensions to leverage the internal BDK retry API +through the `com.symphony.bdk.core.retry.RetryWithRecoveryBuilder` class: +```java +public class MyBdkExtension implements BdkExtension, BdkRetryBuilderAware { + + private RetryWithRecoveryBuilder retryBuilder; + + @Override + public void setRetryBuilder(RetryWithRecoveryBuilder retryBuilder) { + this.retryBuilder = retryBuilder; + } +} +``` diff --git a/docs/index.md b/docs/index.md index e0aebd7ab..56763f70a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,6 +18,7 @@ The reference documentation consists of the following sections: | [Message API](message.md) | Sending or searching messages, usage of templates | | [Datafeed Loop](datafeed.md) | Receiving real time events | | [Activity API](activity-api.md) | The Activity Registry, creating custom activities | +| [Extending the BDK](extension.md) | How to use or develop BDK extensions | ### Spring Boot Getting Started guides are also available for Spring Boot: diff --git a/settings.gradle b/settings.gradle index be8e0a913..9b441cd76 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,19 +3,31 @@ rootProject.name = 'symphony-bdk-java' include(':symphony-bdk-bom') include(':symphony-bdk-core') +include(':symphony-bdk-config') +include(':symphony-bdk-extension-api') + +// http client include(':symphony-bdk-http:symphony-bdk-http-api') include(':symphony-bdk-http:symphony-bdk-http-jersey2') include(':symphony-bdk-http:symphony-bdk-http-webclient') +// template API include(':symphony-bdk-template:symphony-bdk-template-api') include(':symphony-bdk-template:symphony-bdk-template-freemarker') include(':symphony-bdk-template:symphony-bdk-template-handlebars') +// spring wrappers include(':symphony-bdk-spring:symphony-bdk-core-spring-boot-starter') include(':symphony-bdk-spring:symphony-bdk-app-spring-boot-starter') +// examples include(':symphony-bdk-examples:bdk-core-examples') include(':symphony-bdk-examples:bdk-spring-boot-example') include(':symphony-bdk-examples:bdk-template-examples') include(':symphony-bdk-examples:bdk-app-spring-boot-example') include(':symphony-bdk-examples:bdk-multi-instances-example') +include(':symphony-bdk-examples:bdk-group-example') + +// built-in extensions +include(':symphony-bdk-extensions:symphony-group-extension') + diff --git a/symphony-bdk-bom/build.gradle b/symphony-bdk-bom/build.gradle index de9fb9aa6..3eed23358 100644 --- a/symphony-bdk-bom/build.gradle +++ b/symphony-bdk-bom/build.gradle @@ -23,6 +23,8 @@ dependencies { constraints { // Internal modules dependencies (Keep them first) api "org.finos.symphony.bdk:symphony-bdk-core:$project.version" + api "org.finos.symphony.bdk:symphony-bdk-config:$project.version" + api "org.finos.symphony.bdk:symphony-bdk-extension-api:$project.version" api "org.finos.symphony.bdk:symphony-bdk-http-api:$project.version" api "org.finos.symphony.bdk:symphony-bdk-http-jersey2:$project.version" api "org.finos.symphony.bdk:symphony-bdk-http-webclient:$project.version" @@ -31,6 +33,8 @@ dependencies { api "org.finos.symphony.bdk:symphony-bdk-template-api:$project.version" api "org.finos.symphony.bdk:symphony-bdk-template-freemarker:$project.version" api "org.finos.symphony.bdk:symphony-bdk-template-handlebars:$project.version" + // extensions + api "org.finos.symphony.bdk.ext:symphony-group-extension:$project.version" // External dependencies api 'org.projectlombok:lombok:1.18.22' diff --git a/symphony-bdk-config/README.md b/symphony-bdk-config/README.md new file mode 100644 index 000000000..3892f67eb --- /dev/null +++ b/symphony-bdk-config/README.md @@ -0,0 +1,4 @@ +# BDK Configuration +This module contains the logic used to load a `BdkConfig` object from a YAML, JSON or properties file. For more detailed +information, please read [symphony-bdk-java.finos.org/configuration](https://symphony-bdk-java.finos.org/configuration.html). + diff --git a/symphony-bdk-config/build.gradle b/symphony-bdk-config/build.gradle new file mode 100644 index 000000000..6b5534f8c --- /dev/null +++ b/symphony-bdk-config/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'bdk.java-library-conventions' + id 'bdk.java-publish-conventions' +} + +description = 'Symphony Java BDK Core - Configuration' + +dependencies { + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + api 'org.apiguardian:apiguardian-api' + + implementation 'org.slf4j:slf4j-api' + implementation 'commons-io:commons-io' + implementation 'org.apache.commons:commons-lang3' + implementation 'org.apache.commons:commons-text' + + api 'com.fasterxml.jackson.core:jackson-databind' + api 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' + api 'com.fasterxml.jackson.dataformat:jackson-dataformat-properties' + + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'ch.qos.logback:logback-classic' + testImplementation 'org.assertj:assertj-core' +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/BdkConfigLoader.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/BdkConfigLoader.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/BdkConfigLoader.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/BdkConfigLoader.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/BdkConfigParser.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/BdkConfigParser.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/BdkConfigParser.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/BdkConfigParser.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigException.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigException.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigException.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigException.java diff --git a/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigFormatException.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigFormatException.java new file mode 100644 index 000000000..7bcb02dad --- /dev/null +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BdkConfigFormatException.java @@ -0,0 +1,16 @@ +package com.symphony.bdk.core.config.exception; + +import org.apiguardian.api.API; + +/** + * Thrown when a configuration field is not located at the right place in the YAML tree. + * @see com.symphony.bdk.core.config.model.BdkSslConfig + */ +@API(status = API.Status.STABLE) +public class BdkConfigFormatException extends RuntimeException { + + public BdkConfigFormatException(String message) { + super(message); + } + +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/exception/BotNotConfiguredException.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BotNotConfiguredException.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/exception/BotNotConfiguredException.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/exception/BotNotConfiguredException.java diff --git a/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/extension/BdkConfigAware.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/extension/BdkConfigAware.java new file mode 100644 index 000000000..5ed685a7d --- /dev/null +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/extension/BdkConfigAware.java @@ -0,0 +1,20 @@ +package com.symphony.bdk.core.config.extension; + +import com.symphony.bdk.core.config.model.BdkConfig; + +import org.apiguardian.api.API; + +/** + * Interface to be implemented by any {@code com.symphony.bdk.extension.BdkExtension} that wishes to access and read + * the BDK configuration. + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkConfigAware { + + /** + * Set the {@link BdkConfig} object. + * + * @param config the {@code BdkConfig} instance to be used by this object + */ + void setConfiguration(BdkConfig config); +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/LegacyConfigMapper.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/LegacyConfigMapper.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/LegacyConfigMapper.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/LegacyConfigMapper.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacyRetryConfiguration.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacyRetryConfiguration.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacyRetryConfiguration.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacyRetryConfiguration.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacySymConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacySymConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacySymConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/legacy/model/LegacySymConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkAgentConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkAgentConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkAgentConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkAgentConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java similarity index 97% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java index 7d59f27b7..4a33d09b0 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkAuthenticationConfig.java @@ -1,6 +1,6 @@ package com.symphony.bdk.core.config.model; -import static com.symphony.bdk.core.util.DeprecationLogger.logDeprecation; +import static com.symphony.bdk.core.config.util.DeprecationLogger.logDeprecation; import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; import lombok.Getter; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkBotConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkBotConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkBotConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkBotConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java similarity index 91% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java index e5adfaf9c..d7745fb7e 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkCertificateConfig.java @@ -3,14 +3,13 @@ import static org.apache.commons.lang3.ObjectUtils.isEmpty; import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; -import com.symphony.bdk.core.client.exception.ApiClientInitializationException; - import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apiguardian.api.API; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -90,11 +89,11 @@ private byte[] getBytesFromFile(String filePath) { if (resource != null) { return Files.readAllBytes(Paths.get(resource.toURI())); } - throw new ApiClientInitializationException("File not found in classpath: " + filePath); + throw new FileNotFoundException("File not found in classpath: " + filePath); } return Files.readAllBytes(new File(filePath).toPath()); } catch (IOException | URISyntaxException e) { - throw new ApiClientInitializationException("Could not read file " + filePath, e); + throw new IllegalArgumentException("Could not read file " + filePath, e); } } } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkClientConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkClientConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkClientConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkClientConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkCommonJwtConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkCommonJwtConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkCommonJwtConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkCommonJwtConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkDatafeedConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkDatafeedConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkDatafeedConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkDatafeedConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkExtAppConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkExtAppConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkExtAppConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkExtAppConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingMode.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingMode.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingMode.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkLoadBalancingMode.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkProxyConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkProxyConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkProxyConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkProxyConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkRetryConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkRetryConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkRetryConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkRetryConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkRsaKeyConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkRsaKeyConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkRsaKeyConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkRsaKeyConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkServerConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkServerConfig.java similarity index 100% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkServerConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkServerConfig.java diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java similarity index 89% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java rename to symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java index a5c82d20e..73c3a3b28 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/model/BdkSslConfig.java @@ -1,9 +1,9 @@ package com.symphony.bdk.core.config.model; -import static com.symphony.bdk.core.util.DeprecationLogger.logDeprecation; +import static com.symphony.bdk.core.config.util.DeprecationLogger.logDeprecation; import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; -import com.symphony.bdk.core.client.exception.ApiClientInitializationException; +import com.symphony.bdk.core.config.exception.BdkConfigFormatException; import lombok.Getter; import lombok.Setter; @@ -44,7 +44,7 @@ public boolean isValid() { */ public BdkCertificateConfig getCertificateConfig() { if (!isValid()) { - throw new ApiClientInitializationException( + throw new BdkConfigFormatException( "Truststore configuration is not valid. This configuration should only be configured under \"trustStore\" field"); } diff --git a/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/util/DeprecationLogger.java b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/util/DeprecationLogger.java new file mode 100644 index 000000000..29c2e4d8d --- /dev/null +++ b/symphony-bdk-config/src/main/java/com/symphony/bdk/core/config/util/DeprecationLogger.java @@ -0,0 +1,28 @@ +package com.symphony.bdk.core.config.util; + +import org.apiguardian.api.API; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to log deprecation message using "deprecation" logger + */ +@API(status = API.Status.INTERNAL) +public final class DeprecationLogger { + + public static final String LOGGER_NAME = "com.symphony.bdk.deprecation"; + private static final Logger log = LoggerFactory.getLogger(LOGGER_NAME); + + private DeprecationLogger() { + // to avoid instantiation + } + + /** + * Logs a WARN deprecation message + * + * @param message deprecation message to log. + */ + public static void logDeprecation(String message) { + log.warn(message); + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigLoaderTest.java b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigLoaderTest.java similarity index 100% rename from symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigLoaderTest.java rename to symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigLoaderTest.java diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigParserTest.java b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigParserTest.java similarity index 100% rename from symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigParserTest.java rename to symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigParserTest.java diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java similarity index 92% rename from symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java rename to symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java index 9dd2b5e02..7bdd30925 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java +++ b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/BdkConfigTest.java @@ -1,17 +1,13 @@ package com.symphony.bdk.core.config; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -import com.symphony.bdk.core.client.exception.ApiClientInitializationException; import com.symphony.bdk.core.config.model.BdkBotConfig; import com.symphony.bdk.core.config.model.BdkCertificateConfig; import com.symphony.bdk.core.config.model.BdkCommonJwtConfig; import com.symphony.bdk.core.config.model.BdkConfig; import com.symphony.bdk.core.config.model.BdkExtAppConfig; - import com.symphony.bdk.core.config.model.BdkRetryConfig; import org.junit.jupiter.api.Test; @@ -66,7 +62,7 @@ void testBdkCertificateConfigFromClasspath() { void testBdkCertificateConfigNotFoundInClasspath() { BdkCertificateConfig certificateConfig = new BdkCertificateConfig("classpath:/certs/notfound", "password"); - assertThrows(ApiClientInitializationException.class, certificateConfig::getCertificateBytes); + assertThrows(IllegalArgumentException.class, certificateConfig::getCertificateBytes); } private void assertIsCertificateAuthenticationConfigured(String certificatePath, String certificatePassword, diff --git a/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/util/DeprecationLoggerTest.java b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/util/DeprecationLoggerTest.java new file mode 100644 index 000000000..67398182c --- /dev/null +++ b/symphony-bdk-config/src/test/java/com/symphony/bdk/core/config/util/DeprecationLoggerTest.java @@ -0,0 +1,54 @@ +package com.symphony.bdk.core.config.util; + +import static com.symphony.bdk.core.config.util.DeprecationLogger.LOGGER_NAME; +import static com.symphony.bdk.core.config.util.DeprecationLogger.logDeprecation; +import static org.junit.jupiter.api.Assertions.*; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +import java.util.List; + +class DeprecationLoggerTest { + + private ListAppender listAppender; + private Logger logger; + + @BeforeEach + void setUp() { + logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME); + + listAppender = new ListAppender<>(); + listAppender.start(); + + logger.addAppender(listAppender); + } + + @Test + void testDeprecation() { + final String message = "message"; + + logger.setLevel(Level.WARN); + logDeprecation(message); + + List logsList = listAppender.list; + assertEquals(1, logsList.size()); + + final ILoggingEvent loggingEvent = logsList.get(0); + assertEquals(message, loggingEvent.getMessage()); + assertEquals(Level.WARN, loggingEvent.getLevel()); + } + + @Test + void testDeprecationNotLogged() { + logger.setLevel(Level.ERROR); + logDeprecation("message"); + + assertEquals(0, listAppender.list.size()); + } +} diff --git a/symphony-bdk-config/src/test/resources/certs/all_symphony_certs_truststore b/symphony-bdk-config/src/test/resources/certs/all_symphony_certs_truststore new file mode 100644 index 0000000000000000000000000000000000000000..1cb4dcc9d6324c4f0f718150c20decd8652b64a0 GIT binary patch literal 4542 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 literal 0 HcmV?d00001 diff --git a/symphony-bdk-config/src/test/resources/certs/identity.p12 b/symphony-bdk-config/src/test/resources/certs/identity.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c821a874a4fba36e29b378d0cf385a28fb9d746b GIT binary patch literal 4226 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.retryConfig(this.config.getRetry()), + this.config + ); } /** @@ -323,6 +334,11 @@ public BdkConfig config() { return this.config; } + @API(status = API.Status.EXPERIMENTAL) + public ExtensionService extensions() { + return this.extensionService; + } + private T getOrThrowNoBotConfig(T field) { return Optional.ofNullable(field).orElseThrow(BotNotConfiguredException::new); } @@ -336,5 +352,4 @@ protected OboAuthenticator getOboAuthenticator() { return Optional.ofNullable(this.oboAuthenticator) .orElseThrow(() -> new IllegalStateException("OBO is not configured.")); } - } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdkBuilder.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdkBuilder.java index 469dc3ddc..fa69a479a 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdkBuilder.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdkBuilder.java @@ -6,11 +6,15 @@ import com.symphony.bdk.core.client.ApiClientFactory; import com.symphony.bdk.core.config.model.BdkConfig; import com.symphony.bdk.core.util.ServiceLookup; +import com.symphony.bdk.extension.BdkExtension; import com.symphony.bdk.http.api.ApiClientBuilderProvider; import lombok.Generated; import org.apiguardian.api.API; +import java.util.ArrayList; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -28,6 +32,7 @@ public class SymphonyBdkBuilder { private ApiClientBuilderProvider apiClientBuilderProvider; private AuthenticatorFactory authenticatorFactory; private ApiClientFactory apiClientFactory; + private final List> extensions = new ArrayList<>(); /** * With {@link BdkConfig}. @@ -77,6 +82,17 @@ public SymphonyBdkBuilder authenticatorFactory(@Nullable AuthenticatorFactory au return this; } + /** + * Registers a {@link BdkExtension}. + * + * @param extension {@link BdkExtension} class to be registered. + * @return updated builder. + */ + public SymphonyBdkBuilder extension(@Nonnull Class extension) { + this.extensions.add(extension); + return this; + } + /** * Build new {@link SymphonyBdk}. * @@ -102,6 +118,8 @@ public SymphonyBdk build() throws AuthUnauthorizedException, AuthInitializationE this.authenticatorFactory = new AuthenticatorFactory(this.config, this.apiClientFactory); } - return new SymphonyBdk(this.config, this.apiClientFactory, this.authenticatorFactory); + final SymphonyBdk bdk = new SymphonyBdk(this.config, this.apiClientFactory, this.authenticatorFactory); + this.extensions.forEach(bdk.extensions()::register); + return bdk; } } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/AuthenticationRetry.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/AuthenticationRetry.java index 545d5e1fb..58607279f 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/AuthenticationRetry.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/AuthenticationRetry.java @@ -6,7 +6,7 @@ import com.symphony.bdk.core.config.model.BdkRetryConfig; import com.symphony.bdk.core.retry.RetryWithRecovery; import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import com.symphony.bdk.http.api.ApiRuntimeException; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/OAuthentication.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/OAuthentication.java index 42dcd16a0..2eab0003f 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/OAuthentication.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/auth/impl/OAuthentication.java @@ -1,5 +1,6 @@ package com.symphony.bdk.core.auth.impl; -import com.symphony.bdk.core.util.function.SupplierWithApiException; + +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import com.symphony.bdk.http.api.auth.Authentication; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java index f0f07c5bb..e8e545edb 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java @@ -61,12 +61,23 @@ public ApiClient getLoginClient() { } /** - * Returns a fully initialized {@link ApiClient} for Pod API. + * Returns a new pod {@link ApiClient} with "/pod" as context path. * * @return a new {@link ApiClient} instance. */ public ApiClient getPodClient() { - return buildClient(POD_CONTEXT_PATH, this.config.getPod()); + return this.getPodClient(POD_CONTEXT_PATH); + } + + /** + * Returns a new pod {@link ApiClient} for a given context path. + * + * @param contextPath context path. + * @return a new {@link ApiClient} instance. + * @see ApiClientFactory#getPodClient() + */ + public ApiClient getPodClient(String contextPath) { + return buildClient(contextPath, this.config.getPod()); } /** @@ -161,7 +172,8 @@ protected ApiClient buildAgentClient(String basePath, BdkAgentConfig agentConfig return getApiClientBuilder(basePath, agentConfig).build(); } - protected ApiClient buildClientWithCertificate(BdkClientConfig clientConfig, String contextPath, BdkAuthenticationConfig config) { + protected ApiClient buildClientWithCertificate(BdkClientConfig clientConfig, String contextPath, + BdkAuthenticationConfig config) { if (!config.isCertificateAuthenticationConfigured()) { throw new ApiClientInitializationException("For certificate authentication, " + "certificatePath and certificatePassword must be set"); @@ -173,10 +185,11 @@ protected ApiClient buildClientWithCertificate(BdkClientConfig clientConfig, Str apiClient = getApiClientBuilder(clientConfig.getBasePath() + contextPath, clientConfig) .withKeyStore(certificateConfig.getCertificateBytes(), certificateConfig.getPassword()) .build(); - } - catch (IllegalStateException e){ - String failedCertificateMessage = String.format("Failed while trying to parse the certificate at following path: %s." - + " Check configuration is done properly and that certificate is in the correct format.", certificateConfig.getPath()); + } catch (IllegalStateException e) { + String failedCertificateMessage = + String.format("Failed while trying to parse the certificate at following path: %s." + + " Check configuration is done properly and that certificate is in the correct format.", + certificateConfig.getPath()); log.error(failedCertificateMessage); throw new IllegalStateException(failedCertificateMessage, e); } @@ -218,7 +231,12 @@ protected void configureProxy(BdkProxyConfig proxyConfig, ApiClientBuilder apiCl } } - @API(status = API.Status.INTERNAL) + + /** + * @deprecated to be removed if we want to move retry to another module + */ + @Deprecated + @API(status = API.Status.DEPRECATED) public enum ServiceEnum { AGENT, KEY_MANAGER, @@ -226,6 +244,10 @@ public enum ServiceEnum { SESSION_AUTH } + /** + * @deprecated to be removed if we want to move retry to another module + */ + @Deprecated public static ServiceEnum getServiceNameFromBasePath(String basePath) { if (basePath.contains(KEYMANAGER_CONTEXT_PATH) || basePath.contains(KEYAUTH_CONTEXT_PATH)) { return ServiceEnum.KEY_MANAGER; @@ -235,7 +257,8 @@ public static ServiceEnum getServiceNameFromBasePath(String basePath) { } if (basePath.contains(AGENT_CONTEXT_PATH)) { return ServiceEnum.AGENT; - } else { return ServiceEnum.POD; } + } else { + return ServiceEnum.POD; + } } - } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/exception/ApiClientInitializationException.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/exception/ApiClientInitializationException.java index d6a1f183d..0526c0fa7 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/exception/ApiClientInitializationException.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/exception/ApiClientInitializationException.java @@ -22,7 +22,4 @@ public ApiClientInitializationException(String message) { super(message); } - public ApiClientInitializationException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkApiClientFactoryAware.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkApiClientFactoryAware.java new file mode 100644 index 000000000..f129879a3 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkApiClientFactoryAware.java @@ -0,0 +1,21 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.client.ApiClientFactory; + +import org.apiguardian.api.API; + +/** + * Interface to be implemented by any {@link com.symphony.bdk.extension.BdkExtension} that wishes to use the {@link ApiClientFactory}. + * + * @see com.symphony.bdk.extension.BdkExtension + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkApiClientFactoryAware { + + /** + * Set the {@link ApiClientFactory} object. + * + * @param apiClientFactory the {@code ApiClientFactory} instance to be used by this object. + */ + void setApiClientFactory(ApiClientFactory apiClientFactory); +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkAuthenticationAware.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkAuthenticationAware.java new file mode 100644 index 000000000..c4902ac76 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkAuthenticationAware.java @@ -0,0 +1,22 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.auth.AuthSession; + +import org.apiguardian.api.API; + +/** + * Interface to be implemented by any {@link com.symphony.bdk.extension.BdkExtension} that wishes to use the {@link AuthSession} + * of the bot service account. + * + * @see com.symphony.bdk.extension.BdkExtension + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkAuthenticationAware { + + /** + * Set the {@link AuthSession} object. + * + * @param session the {@code AuthSession} instance to be used by this object + */ + void setAuthSession(AuthSession session); +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkRetryBuilderAware.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkRetryBuilderAware.java new file mode 100644 index 000000000..f0c341247 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/BdkRetryBuilderAware.java @@ -0,0 +1,22 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; + +import org.apiguardian.api.API; + +/** + * Interface to be implemented by any {@link com.symphony.bdk.extension.BdkExtension} that wishes to benefit from the + * BDK HTTP Retry logic. + * + * @see com.symphony.bdk.extension.BdkExtension + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkRetryBuilderAware { + + /** + * Set the {@link RetryWithRecoveryBuilder} object. + * + * @param retryBuilder the {@code RetryWithRecoveryBuilder} instance to be used by this object. + */ + void setRetryBuilder(RetryWithRecoveryBuilder retryBuilder); +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/ExtensionService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/ExtensionService.java new file mode 100644 index 000000000..0bf2cb4c7 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/ExtensionService.java @@ -0,0 +1,141 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.config.extension.BdkConfigAware; +import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.core.extension.exception.BdkExtensionException; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.extern.slf4j.Slf4j; +import org.apiguardian.api.API; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Service class for managing extensions. + * + * @see BdkExtension + */ +@Slf4j +@API(status = API.Status.EXPERIMENTAL) +public class ExtensionService { + + private final Map, BdkExtension> extensions; + + private final ApiClientFactory apiClientFactory; + private final AuthSession botSession; + private final RetryWithRecoveryBuilder retryBuilder; + private final BdkConfig config; + + public ExtensionService( + @Nonnull ApiClientFactory apiClientFactory, + @Nullable AuthSession botSession, + @Nonnull RetryWithRecoveryBuilder retryBuilder, + @Nonnull BdkConfig config + ) { + this.apiClientFactory = apiClientFactory; + this.botSession = botSession; + this.retryBuilder = retryBuilder; + this.config = config; + this.extensions = Collections.synchronizedMap(new HashMap<>()); + } + + public void register(BdkExtension extension) { + + final Class extClz = extension.getClass(); + + this.checkAlreadyRegistered(extClz); + + if (extension instanceof BdkAuthenticationAware) { + if (this.botSession == null) { + log.info("Extension <{}> uses authentication, but it has not been configured in BDK config", extClz); + } else { + log.debug("Extension <{}> uses authentication", extClz); + ((BdkAuthenticationAware) extension).setAuthSession(this.botSession); + } + } + + if (extension instanceof BdkApiClientFactoryAware) { + log.debug("Extension <{}> uses the ApiClientFactory", extClz); + ((BdkApiClientFactoryAware) extension).setApiClientFactory(this.apiClientFactory); + } + + if (extension instanceof BdkRetryBuilderAware) { + log.debug("Extension <{}> uses the RetryBuilder", extClz); + ((BdkRetryBuilderAware) extension).setRetryBuilder(this.retryBuilder); + } + + if (extension instanceof BdkConfigAware) { + log.debug("Extension <{}> uses the configuration", extClz); + ((BdkConfigAware) extension).setConfiguration(this.config); + } + + this.extensions.put(extClz, extension); + } + + /** + * Registers and instantiates an extension. + * + * @param extClz Type of the extension. + * @throws IllegalStateException if the extension has already been registered + * @throws BdkExtensionException if the extension cannot be instantiated + * @see BdkExtension + */ + public void register(Class extClz) { + log.debug("Registering extension <{}>", extClz); + + this.checkAlreadyRegistered(extClz); + + BdkExtension extension; + + try { + extension = extClz.getConstructor().newInstance(); + log.debug("Extension {} successfully instantiated", extClz); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new BdkExtensionException("Extension <" + extClz + "> must have a default constructor", e); + } + + this.register(extension); + } + + /** + * Retrieves an extension service instance. + * + * @param extClz The extension class. + * @param Type of the extension service. + * @param Type of the extension. + * + * @return extension service instance. + * @throws IllegalStateException if the extension is not registered + * @see BdkExtension + * @see BdkExtensionServiceProvider + * @see ExtensionService#register(Class) + */ + @SuppressWarnings("unchecked") + public > S service(Class extClz) { + + final BdkExtension extension = this.extensions.get(extClz); + + if (extension == null) { + throw new IllegalStateException("Extension <" + extClz + "> is not registered"); + } + + return ((BdkExtensionServiceProvider) extension).getService(); + } + + private void checkAlreadyRegistered(Class extClz) { + if (this.extensions.get(extClz) != null) { + throw new IllegalStateException("Extension <" + extClz + "> has already been registered"); + } + } +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/exception/BdkExtensionException.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/exception/BdkExtensionException.java new file mode 100644 index 000000000..635a1f4c5 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/extension/exception/BdkExtensionException.java @@ -0,0 +1,11 @@ +package com.symphony.bdk.core.extension.exception; + +import org.apiguardian.api.API; + +@API(status = API.Status.EXPERIMENTAL) +public class BdkExtensionException extends RuntimeException { + + public BdkExtensionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RecoveryStrategy.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RecoveryStrategy.java index 0eed652a9..94d8ab9c7 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RecoveryStrategy.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RecoveryStrategy.java @@ -1,6 +1,6 @@ package com.symphony.bdk.core.retry; -import com.symphony.bdk.core.util.function.ConsumerWithThrowable; +import com.symphony.bdk.core.retry.function.ConsumerWithThrowable; import org.apiguardian.api.API; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecovery.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecovery.java index 217b929b8..3496291aa 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecovery.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecovery.java @@ -2,7 +2,7 @@ import com.symphony.bdk.core.client.ApiClientFactory; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import com.symphony.bdk.http.api.ApiRuntimeException; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecoveryBuilder.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecoveryBuilder.java index 3b85b3bcf..2054915d8 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecoveryBuilder.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/RetryWithRecoveryBuilder.java @@ -2,8 +2,8 @@ import com.symphony.bdk.core.config.model.BdkRetryConfig; import com.symphony.bdk.core.retry.resilience4j.Resilience4jRetryWithRecovery; -import com.symphony.bdk.core.util.function.ConsumerWithThrowable; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.ConsumerWithThrowable; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import org.apiguardian.api.API; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/ConsumerWithThrowable.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/ConsumerWithThrowable.java similarity index 87% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/ConsumerWithThrowable.java rename to symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/ConsumerWithThrowable.java index 45b9fe7ab..f6df94558 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/ConsumerWithThrowable.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/ConsumerWithThrowable.java @@ -1,5 +1,4 @@ -package com.symphony.bdk.core.util.function; - +package com.symphony.bdk.core.retry.function; import org.apiguardian.api.API; @@ -10,5 +9,6 @@ @FunctionalInterface @API(status = API.Status.INTERNAL) public interface ConsumerWithThrowable { + void consume() throws Throwable; } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/SupplierWithApiException.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/SupplierWithApiException.java similarity index 88% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/SupplierWithApiException.java rename to symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/SupplierWithApiException.java index 8e91be276..73ccaadd1 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/function/SupplierWithApiException.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/function/SupplierWithApiException.java @@ -1,5 +1,4 @@ -package com.symphony.bdk.core.util.function; - +package com.symphony.bdk.core.retry.function; import com.symphony.bdk.http.api.ApiException; @@ -13,5 +12,6 @@ @FunctionalInterface @API(status = API.Status.INTERNAL) public interface SupplierWithApiException { + T get() throws ApiException; } diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecovery.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecovery.java index 83de701a8..5d4237062 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecovery.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecovery.java @@ -3,8 +3,8 @@ import com.symphony.bdk.core.config.model.BdkRetryConfig; import com.symphony.bdk.core.retry.RecoveryStrategy; import com.symphony.bdk.core.retry.RetryWithRecovery; -import com.symphony.bdk.core.util.BdkExponentialFunction; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.util.BdkExponentialFunction; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import io.github.resilience4j.retry.Retry; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/BdkExponentialFunction.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/util/BdkExponentialFunction.java similarity index 97% rename from symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/BdkExponentialFunction.java rename to symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/util/BdkExponentialFunction.java index 85fe36d4f..cd8d59ed3 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/BdkExponentialFunction.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/retry/util/BdkExponentialFunction.java @@ -1,4 +1,4 @@ -package com.symphony.bdk.core.util; +package com.symphony.bdk.core.retry.util; import com.symphony.bdk.core.config.model.BdkRetryConfig; import io.github.resilience4j.core.IntervalFunction; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/application/ApplicationService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/application/ApplicationService.java index c5e2c610d..f158205d1 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/application/ApplicationService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/application/ApplicationService.java @@ -3,7 +3,7 @@ import com.symphony.bdk.core.auth.AuthSession; import com.symphony.bdk.core.retry.RetryWithRecovery; import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.AppEntitlementApi; import com.symphony.bdk.gen.api.ApplicationApi; import com.symphony.bdk.gen.api.model.ApplicationDetail; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/connection/ConnectionService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/connection/ConnectionService.java index f079ad0db..cd0ca7bdb 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/connection/ConnectionService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/connection/ConnectionService.java @@ -5,7 +5,7 @@ import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; import com.symphony.bdk.core.service.OboService; import com.symphony.bdk.core.service.connection.constant.ConnectionStatus; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.ConnectionApi; import com.symphony.bdk.gen.api.model.UserConnection; import com.symphony.bdk.gen.api.model.UserConnectionRequest; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/disclaimer/DisclaimerService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/disclaimer/DisclaimerService.java index 2c7c4a179..39cfddc5a 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/disclaimer/DisclaimerService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/disclaimer/DisclaimerService.java @@ -3,7 +3,7 @@ import com.symphony.bdk.core.auth.AuthSession; import com.symphony.bdk.core.retry.RetryWithRecovery; import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.DisclaimerApi; import com.symphony.bdk.gen.api.model.Disclaimer; import com.symphony.bdk.http.api.ApiException; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/health/HealthService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/health/HealthService.java index 75249dad1..e3e2daecf 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/health/HealthService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/health/HealthService.java @@ -1,7 +1,7 @@ package com.symphony.bdk.core.service.health; import com.symphony.bdk.core.auth.AuthSession; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.SignalsApi; import com.symphony.bdk.gen.api.SystemApi; import com.symphony.bdk.gen.api.model.AgentInfo; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/message/MessageService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/message/MessageService.java index afda5e6af..bef4d167a 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/message/MessageService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/message/MessageService.java @@ -14,7 +14,7 @@ import com.symphony.bdk.core.service.message.model.SortDir; import com.symphony.bdk.core.service.pagination.model.PaginationAttribute; import com.symphony.bdk.core.service.stream.constant.AttachmentSort; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.AttachmentsApi; import com.symphony.bdk.gen.api.DefaultApi; import com.symphony.bdk.gen.api.MessageApi; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/presence/PresenceService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/presence/PresenceService.java index 893d9db2a..75173079e 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/presence/PresenceService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/presence/PresenceService.java @@ -5,7 +5,7 @@ import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; import com.symphony.bdk.core.service.OboService; import com.symphony.bdk.core.service.presence.constant.PresenceStatus; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.PresenceApi; import com.symphony.bdk.gen.api.model.V2Presence; import com.symphony.bdk.gen.api.model.V2PresenceStatus; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/session/SessionService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/session/SessionService.java index a1517e09e..889e99938 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/session/SessionService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/session/SessionService.java @@ -4,7 +4,7 @@ import com.symphony.bdk.core.retry.RetryWithRecovery; import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; import com.symphony.bdk.core.service.OboService; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.SessionApi; import com.symphony.bdk.gen.api.model.UserV2; import com.symphony.bdk.http.api.ApiException; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/signal/SignalService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/signal/SignalService.java index 52bc108e3..945cd68e4 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/signal/SignalService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/signal/SignalService.java @@ -8,7 +8,7 @@ import com.symphony.bdk.core.service.pagination.PaginatedService; import com.symphony.bdk.core.service.pagination.model.PaginationAttribute; import com.symphony.bdk.core.service.pagination.model.StreamPaginationAttribute; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.SignalsApi; import com.symphony.bdk.gen.api.model.BaseSignal; import com.symphony.bdk.gen.api.model.ChannelSubscriber; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java index 8e6095bac..1c25b6587 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java @@ -10,7 +10,7 @@ import com.symphony.bdk.core.service.pagination.PaginatedService; import com.symphony.bdk.core.service.pagination.model.PaginationAttribute; import com.symphony.bdk.core.service.pagination.model.StreamPaginationAttribute; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.RoomMembershipApi; import com.symphony.bdk.gen.api.ShareApi; import com.symphony.bdk.gen.api.StreamsApi; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/user/UserService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/user/UserService.java index b2a5bbc35..2079a7f0f 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/user/UserService.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/user/UserService.java @@ -11,7 +11,7 @@ import com.symphony.bdk.core.service.pagination.model.StreamPaginationAttribute; import com.symphony.bdk.core.service.user.constant.RoleId; import com.symphony.bdk.core.service.user.mapper.UserDetailMapper; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.gen.api.AuditTrailApi; import com.symphony.bdk.gen.api.UserApi; import com.symphony.bdk.gen.api.UsersApi; diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/UserIdUtil.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/UserIdUtil.java new file mode 100644 index 000000000..ee49b1a74 --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/util/UserIdUtil.java @@ -0,0 +1,84 @@ +package com.symphony.bdk.core.util; + +import org.apiguardian.api.API; + +/** + * Used to extract the tenant ID from a user ID. + *

+ * The user ID is a combination of a unique tenant ID, and a unique sub-tenant ID, combined into a long. + * The tenant ID is stored in the 27 highest bits (minus the sign bit which is unused so that all IDs remain a positive value) + * which allows for 134 million pods. + * This leaves 36 lowest bits for the user ID, which allows 68.7 billion users per tenant. + */ +@API(status = API.Status.STABLE) +public class UserIdUtil { + + private static final int TENANT_ID_BIT_LENGTH = 27; + private static final int SUBTENANT_ID_BIT_LENGTH = 36; + private static final int TENANT_ID_INDEX = 1; + + private static final LongUtil USERID_UTIL = new LongUtil(SUBTENANT_ID_BIT_LENGTH, TENANT_ID_BIT_LENGTH); + + private UserIdUtil() { + // nothing to be done here + } + + /** + * Extracts the tenant ID from a user ID. + * + * @param userId the user ID. + * @return the tenant ID. + */ + public static int extractTenantId(long userId) { + return (int) USERID_UTIL.extract(userId, TENANT_ID_INDEX); + } + + @API(status = API.Status.INTERNAL) + static class LongUtil { + + private final Segment[] segments; + + public LongUtil(int... sizes) { + this.segments = new Segment[sizes.length]; + short totalSize = 0; + short shift = 0; + + for (int i = 0; i < sizes.length; ++i) { + short size = (short) sizes[i]; + Segment segment = new Segment(size, shift); + shift += size; + this.segments[i] = segment; + totalSize += size; + } + + if (totalSize > 64) { + throw new IllegalArgumentException("total size is larger than the bit-count of a long"); + } + } + + public long extract(long value, int index) { + Segment s = this.segments[index]; + return value >> s.shift & s.mask; + } + + @API(status = API.Status.INTERNAL) + static class Segment { + + private final long mask; + private final short shift; + + Segment(short size, short shift) { + long l = 0L; + + for (int i = 0; i < size; ++i) { + l |= 1L; + l <<= 1; + } + + l >>= 1; + this.mask = l; + this.shift = shift; + } + } + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/auth/impl/AuthenticationRetryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/auth/impl/AuthenticationRetryTest.java index 5918fceae..9034e36cc 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/auth/impl/AuthenticationRetryTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/auth/impl/AuthenticationRetryTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.when; import com.symphony.bdk.core.auth.exception.AuthUnauthorizedException; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import com.symphony.bdk.http.api.ApiRuntimeException; diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java index 04448aa8e..0543131bc 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java @@ -1,7 +1,6 @@ package com.symphony.bdk.core.client; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -9,6 +8,7 @@ import com.symphony.bdk.core.client.exception.ApiClientInitializationException; import com.symphony.bdk.core.client.loadbalancing.DatafeedLoadBalancedApiClient; import com.symphony.bdk.core.client.loadbalancing.RegularLoadBalancedApiClient; +import com.symphony.bdk.core.config.exception.BdkConfigFormatException; import com.symphony.bdk.core.config.model.BdkConfig; import com.symphony.bdk.core.config.model.BdkLoadBalancingConfig; import com.symphony.bdk.core.config.model.BdkLoadBalancingMode; @@ -105,8 +105,8 @@ void testKeyAuthClientWithoutCertificateConfiguredShouldFail() { void testAuthClientWithWrongCertPathShouldFail() { BdkConfig bdkConfig = this.createConfigWithCertificate("./non/existent/file.p12", "password"); - assertThrows(ApiClientInitializationException.class, () -> new ApiClientFactory(bdkConfig).getSessionAuthClient()); - assertThrows(ApiClientInitializationException.class, () -> new ApiClientFactory(bdkConfig).getKeyAuthClient()); + assertThrows(IllegalArgumentException.class, () -> new ApiClientFactory(bdkConfig).getSessionAuthClient()); + assertThrows(IllegalArgumentException.class, () -> new ApiClientFactory(bdkConfig).getKeyAuthClient()); } @Test @@ -155,7 +155,7 @@ void testExtAppAuthClientWithWrongCertPathShouldFail() { BdkConfig bdkConfig = this.createConfig(); this.addExtAppCertificateToConfig(bdkConfig, "./non/existent/file.p12", "password"); - assertThrows(ApiClientInitializationException.class, + assertThrows(IllegalArgumentException.class, () -> new ApiClientFactory(bdkConfig).getExtAppSessionAuthClient()); } @@ -191,9 +191,9 @@ void testAuthClientWithWrongTrustStorePathShouldFail() { BdkConfig configWithTrustStore = this.createConfigWithCertificateAndTrustStore("./src/test/resources/certs/non_existing_truststore", "changeit"); - assertThrows(ApiClientInitializationException.class, + assertThrows(IllegalArgumentException.class, () -> new ApiClientFactory(configWithTrustStore).getSessionAuthClient()); - assertThrows(ApiClientInitializationException.class, + assertThrows(IllegalArgumentException.class, () -> new ApiClientFactory(configWithTrustStore).getKeyAuthClient()); } @@ -215,7 +215,7 @@ void testAuthClientWithInvalidTrustStoreConfigShouldFail() { configWithTrustStore.getSsl().getTrustStore().setContent("content".getBytes()); - assertThrows(ApiClientInitializationException.class, () -> new ApiClientFactory(configWithTrustStore).getSessionAuthClient()); + assertThrows(BdkConfigFormatException.class, () -> new ApiClientFactory(configWithTrustStore).getSessionAuthClient()); } @Test @@ -227,7 +227,7 @@ void testAuthClientWithInvalidTrustStoreAndTrustStorePathConfigShouldFail() { configWithTrustStore.getSsl().setTrustStorePath("./src/test/resources/certs/all_symphony_certs_truststore"); configWithTrustStore.getSsl().setTrustStorePassword("changeit"); - assertThrows(ApiClientInitializationException.class, () -> new ApiClientFactory(configWithTrustStore).getSessionAuthClient()); + assertThrows(BdkConfigFormatException.class, () -> new ApiClientFactory(configWithTrustStore).getSessionAuthClient()); } @Test diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/ExtensionServiceTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/ExtensionServiceTest.java new file mode 100644 index 000000000..7f652aad5 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/ExtensionServiceTest.java @@ -0,0 +1,92 @@ +package com.symphony.bdk.core.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.core.extension.exception.BdkExtensionException; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ExtensionServiceTest { + + @Mock + private BdkConfig config; + @Mock + private ApiClientFactory apiClientFactory; + @Mock + private AuthSession authSession; + @Mock + private RetryWithRecoveryBuilder retryBuilder; + + @InjectMocks + private ExtensionService extensionService; + + @Test + void shouldRegisterExtensionService() { + this.extensionService.register(TestExtensionWithService.class); + assertThat(this.extensionService.service(TestExtensionWithService.class)).isNotNull(); + } + + @Test + void shouldFailToGetServiceIfExtensionWasNotRegistered() { + IllegalStateException ex = + assertThrows(IllegalStateException.class, () -> this.extensionService.service(TestExtensionWithService.class)); + assertThat(ex).hasMessage("Extension <" + TestExtensionWithService.class + "> is not registered"); + } + + @Test + void shouldFailToRegisterExtensionWithoutDefaultConstructor() { + BdkExtensionException ex = assertThrows(BdkExtensionException.class, + () -> this.extensionService.register(TestExtensionWithoutDefaultConstructor.class)); + assertThat(ex).hasMessage("Extension <" + TestExtensionWithoutDefaultConstructor.class + "> must have a default constructor"); + } + + @Test + void shouldNotRegisterTwiceAnExtension() { + this.extensionService.register(TestExtensionWithService.class); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> this.extensionService.register(TestExtensionWithService.class)); + assertThat(ex).hasMessage("Extension <" + TestExtensionWithService.class + "> has already been registered"); + } + + @Test + void shouldNotRegisterTwiceAnExtensionInstance() { + this.extensionService.register(new TestExtensionWithService()); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> this.extensionService.register(new TestExtensionWithService())); + assertThat(ex).hasMessage("Extension <" + TestExtensionWithService.class + "> has already been registered"); + } + + @Test + void shouldRegisterConfigAwareExtension() { + this.extensionService.register(TestExtensionConfigAware.class); + assertThat(this.extensionService.service(TestExtensionConfigAware.class).getConfig()).isEqualTo(this.config); + } + + @Test + void shouldRegisterApiClientFactoryAwareExtension() { + this.extensionService.register(TestExtensionApiClientFactoryAware.class); + assertThat(this.extensionService.service(TestExtensionApiClientFactoryAware.class).getApiClientFactory()).isEqualTo(this.apiClientFactory); + } + + @Test + void shouldRegisterAuthenticationAwareExtension() { + this.extensionService.register(TestExtensionAuthenticationAware.class); + assertThat(this.extensionService.service(TestExtensionAuthenticationAware.class).getAuthSession()).isEqualTo(this.authSession); + } + + @Test + void shouldRegisterRetryBuilderAwareExtension() { + this.extensionService.register(TestExtensionRetryBuilderAware.class); + assertThat(this.extensionService.service(TestExtensionRetryBuilderAware.class).getRetryBuilder()).isEqualTo(this.retryBuilder); + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionApiClientFactoryAware.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionApiClientFactoryAware.java new file mode 100644 index 000000000..8ff432ee8 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionApiClientFactoryAware.java @@ -0,0 +1,31 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.Getter; +import lombok.Setter; + +public class TestExtensionApiClientFactoryAware implements BdkExtension, BdkApiClientFactoryAware, + BdkExtensionServiceProvider { + + @Getter + @Setter + public static class TestService implements BdkExtensionService { + ApiClientFactory apiClientFactory; + } + + private final TestService service = new TestService(); + + @Override + public void setApiClientFactory(ApiClientFactory apiClientFactory) { + this.service.setApiClientFactory(apiClientFactory); + } + + @Override + public TestService getService() { + return this.service; + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionAuthenticationAware.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionAuthenticationAware.java new file mode 100644 index 000000000..e7ee6f44f --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionAuthenticationAware.java @@ -0,0 +1,31 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.Getter; +import lombok.Setter; + +public class TestExtensionAuthenticationAware implements BdkExtension, BdkAuthenticationAware, + BdkExtensionServiceProvider { + + @Getter + @Setter + public static class TestService implements BdkExtensionService { + AuthSession authSession; + } + + private final TestService service = new TestService(); + + @Override + public void setAuthSession(AuthSession session) { + this.service.setAuthSession(session); + } + + @Override + public TestService getService() { + return this.service; + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionConfigAware.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionConfigAware.java new file mode 100644 index 000000000..ba961da0c --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionConfigAware.java @@ -0,0 +1,31 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.config.extension.BdkConfigAware; +import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.extension.BdkExtension; + +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.Getter; +import lombok.Setter; + +public class TestExtensionConfigAware implements BdkExtension, BdkConfigAware, BdkExtensionServiceProvider { + + @Getter @Setter + public static class ConfigService implements BdkExtensionService { + BdkConfig config; + } + + private final ConfigService service = new ConfigService(); + + @Override + public void setConfiguration(BdkConfig config) { + this.service.setConfig(config); + } + + @Override + public ConfigService getService() { + return this.service; + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionRetryBuilderAware.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionRetryBuilderAware.java new file mode 100644 index 000000000..0d0e91b24 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionRetryBuilderAware.java @@ -0,0 +1,31 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.Getter; +import lombok.Setter; + +public class TestExtensionRetryBuilderAware implements BdkExtension, BdkRetryBuilderAware, + BdkExtensionServiceProvider { + + @Getter + @Setter + public static class TestService implements BdkExtensionService { + RetryWithRecoveryBuilder retryBuilder; + } + + private final TestService service = new TestService(); + + @Override + public void setRetryBuilder(RetryWithRecoveryBuilder retryBuilder) { + this.service.setRetryBuilder(retryBuilder); + } + + @Override + public TestService getService() { + return this.service; + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithService.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithService.java new file mode 100644 index 000000000..2a0db1607 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithService.java @@ -0,0 +1,16 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +public class TestExtensionWithService implements BdkExtension, + BdkExtensionServiceProvider { + + public static class TestExtensionService implements BdkExtensionService {} + + @Override + public TestExtensionService getService() { + return new TestExtensionService(); + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithoutDefaultConstructor.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithoutDefaultConstructor.java new file mode 100644 index 000000000..633daef10 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/extension/TestExtensionWithoutDefaultConstructor.java @@ -0,0 +1,8 @@ +package com.symphony.bdk.core.extension; + +import com.symphony.bdk.extension.BdkExtension; + +public class TestExtensionWithoutDefaultConstructor implements BdkExtension { + + public TestExtensionWithoutDefaultConstructor(String data) {} +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecoveryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecoveryTest.java index 9f76ed998..efef6048e 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecoveryTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/retry/resilience4j/Resilience4jRetryWithRecoveryTest.java @@ -16,8 +16,8 @@ import com.symphony.bdk.core.config.model.BdkRetryConfig; import com.symphony.bdk.core.retry.RecoveryStrategy; import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; -import com.symphony.bdk.core.util.function.ConsumerWithThrowable; -import com.symphony.bdk.core.util.function.SupplierWithApiException; +import com.symphony.bdk.core.retry.function.ConsumerWithThrowable; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; import com.symphony.bdk.http.api.ApiException; import com.symphony.bdk.http.api.ApiRuntimeException; diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/BdkExponentialFunctionTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/BdkExponentialFunctionTest.java index 42b7395b9..67a3e7fa1 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/BdkExponentialFunctionTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/BdkExponentialFunctionTest.java @@ -1,6 +1,7 @@ package com.symphony.bdk.core.util; import com.symphony.bdk.core.config.model.BdkRetryConfig; +import com.symphony.bdk.core.retry.util.BdkExponentialFunction; import io.github.resilience4j.core.IntervalFunction; import org.junit.jupiter.api.Test; diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/UserIdUtilTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/UserIdUtilTest.java new file mode 100644 index 000000000..54405cbdf --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/util/UserIdUtilTest.java @@ -0,0 +1,24 @@ +package com.symphony.bdk.core.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class UserIdUtilTest { + + @Test + void shouldExtractTenantId() { + final int tenantId = 189; + final long userId = 12987981103203L; + assertThat(UserIdUtil.extractTenantId(userId)).isEqualTo(tenantId); + } + + @Test + void testIllegalSegmentsSize() { + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> new UserIdUtil.LongUtil(32, 32, 32)); + assertThat(ex).hasMessage("total size is larger than the bit-count of a long"); + } +} diff --git a/symphony-bdk-examples/bdk-group-example/build.gradle b/symphony-bdk-examples/bdk-group-example/build.gradle new file mode 100644 index 000000000..8fd144edb --- /dev/null +++ b/symphony-bdk-examples/bdk-group-example/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'bdk.java-common-conventions' +} + +description = 'Symphony Java BDK Examples - Core' + +dependencies { + + implementation project(':symphony-bdk-core') + + runtimeOnly project(':symphony-bdk-template:symphony-bdk-template-freemarker') + runtimeOnly project(':symphony-bdk-http:symphony-bdk-http-jersey2') + + // import the extension + implementation project(':symphony-bdk-extensions:symphony-group-extension') + + // logging + implementation 'org.slf4j:slf4j-api' + runtimeOnly 'ch.qos.logback:logback-classic' +} diff --git a/symphony-bdk-examples/bdk-group-example/src/main/java/com/symphony/bdk/examples/GroupExtensionExample.java b/symphony-bdk-examples/bdk-group-example/src/main/java/com/symphony/bdk/examples/GroupExtensionExample.java new file mode 100644 index 000000000..48aea850b --- /dev/null +++ b/symphony-bdk-examples/bdk-group-example/src/main/java/com/symphony/bdk/examples/GroupExtensionExample.java @@ -0,0 +1,82 @@ +package com.symphony.bdk.examples; + +import static com.symphony.bdk.core.activity.command.SlashCommand.slash; +import static com.symphony.bdk.core.config.BdkConfigLoader.loadFromSymphonyDir; + +import com.symphony.bdk.core.SymphonyBdk; +import com.symphony.bdk.core.activity.parsing.Mention; +import com.symphony.bdk.core.service.message.model.Message; +import com.symphony.bdk.core.util.UserIdUtil; +import com.symphony.bdk.ext.group.SymphonyGroupBdkExtension; +import com.symphony.bdk.ext.group.SymphonyGroupService; +import com.symphony.bdk.ext.group.gen.api.model.AddMember; +import com.symphony.bdk.ext.group.gen.api.model.GroupList; +import com.symphony.bdk.ext.group.gen.api.model.Member; +import com.symphony.bdk.ext.group.gen.api.model.ReadGroup; +import com.symphony.bdk.ext.group.gen.api.model.Status; +import com.symphony.bdk.http.api.ApiRuntimeException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +/** + * Example of usage of the Groups API. The bot bellow curretnly provides 2 different commands: + *

    + *
  • {@code /groups} to list available active groups
  • + *
  • {@code /groups {groupId} add @member} to list available active groups
  • + *
+ */ +@SuppressWarnings("all") +public class GroupExtensionExample { + + private static final Logger log = LoggerFactory.getLogger(GroupExtensionExample.class); + + private static final String TYPE_SDL = "SDL"; + + public static void main(String[] args) throws Exception { + + final SymphonyBdk bdk = SymphonyBdk.builder() + .config(loadFromSymphonyDir("config.yaml")) + .extension(SymphonyGroupBdkExtension.class) // or bdk.extensions().register(SymphonyGroupBdkExtension.class); + .build(); + + // extension is also service provider + final SymphonyGroupService groupService = bdk.extensions().service(SymphonyGroupBdkExtension.class); + + // list groups + bdk.activities().register(slash("/groups", false, c -> { + final GroupList groups = groupService.listGroups(TYPE_SDL, Status.ACTIVE, null, null, null, null); + bdk.messages().send(c.getStreamId(), Message.builder() + .template(bdk.messages().templates().newTemplateFromClasspath("/groups.ftl"), groups) + .build()); + })); + + // add member to group + bdk.activities().register(slash("/groups {groupId} add {@member}", false, c -> { + + final String groupId = c.getArguments().getString("groupId"); + final Mention member = c.getArguments().getMention("member"); + + Optional group = getGroup(groupService, groupId); + + if (group.isPresent()) { + groupService.addMemberToGroup(groupId, new AddMember().member(new Member().memberId(member.getUserId()).memberTenant(UserIdUtil.extractTenantId(member.getUserId())))); + bdk.messages().send(c.getStreamId(), "Member " + member.getUserDisplayName() + " successfully added to group " + group.get().getName() + ""); + } else { + bdk.messages().send(c.getStreamId(), "Group " + groupId + " not found."); + } + })); + + bdk.datafeed().start(); + } + + private static Optional getGroup(SymphonyGroupService groupService, String groupId) { + try { + return Optional.of(groupService.getGroup(groupId)); + } catch (ApiRuntimeException ex) { + return Optional.empty(); + } + } +} diff --git a/symphony-bdk-examples/bdk-group-example/src/main/resources/groups.ftl b/symphony-bdk-examples/bdk-group-example/src/main/resources/groups.ftl new file mode 100644 index 000000000..2fe733fc8 --- /dev/null +++ b/symphony-bdk-examples/bdk-group-example/src/main/resources/groups.ftl @@ -0,0 +1,15 @@ + +

List of groups

+ The following list contains the group name and the group id +
+
    + <#list data as group> +
  • ${group.name} (${group.id})
  • + +
+
+ You can add a new member to an existing group using the following command: + + /groups {groupId} add @member + +
diff --git a/symphony-bdk-examples/bdk-spring-boot-example/build.gradle b/symphony-bdk-examples/bdk-spring-boot-example/build.gradle index 5dcdc9bec..efd7729b3 100644 --- a/symphony-bdk-examples/bdk-spring-boot-example/build.gradle +++ b/symphony-bdk-examples/bdk-spring-boot-example/build.gradle @@ -9,8 +9,12 @@ dependencies { implementation project(':symphony-bdk-spring:symphony-bdk-core-spring-boot-starter') + // import the extension + implementation project(':symphony-bdk-extensions:symphony-group-extension') + implementation 'org.apache.commons:commons-lang3' implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/ApiExceptionHandler.java b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/ApiExceptionHandler.java new file mode 100644 index 000000000..3409600d6 --- /dev/null +++ b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/ApiExceptionHandler.java @@ -0,0 +1,30 @@ +package com.symphony.bdk.examples.spring.api; + +import com.symphony.bdk.http.api.ApiRuntimeException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +@RequiredArgsConstructor +public class ApiExceptionHandler { + + private final ObjectMapper objectMapper; + + @Data + static class ErrorMessage { + private String code; + private String message; + } + + @ExceptionHandler(value = ApiRuntimeException.class) + public ResponseEntity resourceNotFoundException(final ApiRuntimeException ex) throws JsonProcessingException { + return new ResponseEntity<>(this.objectMapper.readValue(ex.getResponseBody(), ErrorMessage.class), HttpStatus.valueOf(ex.getCode())); + } +} diff --git a/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/GroupApi.java b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/GroupApi.java new file mode 100644 index 000000000..22c1f0854 --- /dev/null +++ b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/api/GroupApi.java @@ -0,0 +1,36 @@ +package com.symphony.bdk.examples.spring.api; + +import com.symphony.bdk.ext.group.SymphonyGroupService; +import com.symphony.bdk.ext.group.gen.api.model.GroupList; +import com.symphony.bdk.ext.group.gen.api.model.Status; +import com.symphony.bdk.ext.group.gen.api.model.TypeList; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/api/v1/groups") +public class GroupApi { + + private final SymphonyGroupService groupService; + + @Autowired + public GroupApi(SymphonyGroupService groupService) { + this.groupService = groupService; + } + + @GetMapping + public GroupList getGroups(@RequestParam(defaultValue = "SDL") String type) { + return this.groupService.listGroups(type, Status.ACTIVE, null, null, null, null); + } + + @GetMapping("/types") + public TypeList getTypes() { + return this.groupService.listTypes(Status.ACTIVE, null, null, null, null); + } +} diff --git a/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/config/GroupExtensionConfig.java b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/config/GroupExtensionConfig.java new file mode 100644 index 000000000..08061f1f7 --- /dev/null +++ b/symphony-bdk-examples/bdk-spring-boot-example/src/main/java/com/symphony/bdk/examples/spring/config/GroupExtensionConfig.java @@ -0,0 +1,22 @@ +package com.symphony.bdk.examples.spring.config; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.ext.group.SymphonyGroupService; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class GroupExtensionConfig { + + @Bean + public SymphonyGroupService groupService( + final RetryWithRecoveryBuilder retryBuilder, + final ApiClientFactory apiClientFactory, + final AuthSession session + ) { + return new SymphonyGroupService(retryBuilder, apiClientFactory, session); + } +} diff --git a/symphony-bdk-extension-api/build.gradle b/symphony-bdk-extension-api/build.gradle new file mode 100644 index 000000000..aaf94f5f1 --- /dev/null +++ b/symphony-bdk-extension-api/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'bdk.java-library-conventions' + id 'bdk.java-publish-conventions' +} + +description = 'Symphony Java BDK Core - Extension API' + +dependencies { + + api 'org.apiguardian:apiguardian-api' + +} diff --git a/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtension.java b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtension.java new file mode 100644 index 000000000..1dab2cb8b --- /dev/null +++ b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtension.java @@ -0,0 +1,14 @@ +package com.symphony.bdk.extension; + +import org.apiguardian.api.API; + +/** + * Marker interface for all BDK extensions. + * + *

An extension can be manually registered using {@code bdk.extensions().register(Class>? extends BdkExtension<)} method. + * + *

An extension must have a default constructor in order to be automatically instantiated by the BDK {@code ExtensionService}. + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkExtension { +} diff --git a/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionService.java b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionService.java new file mode 100644 index 000000000..1c504983a --- /dev/null +++ b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionService.java @@ -0,0 +1,10 @@ +package com.symphony.bdk.extension; + +/** + * Marker interface for all extension's services. + * + * @see BdkExtension + * @see BdkExtensionServiceProvider + */ +public interface BdkExtensionService { +} diff --git a/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionServiceProvider.java b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionServiceProvider.java new file mode 100644 index 000000000..b32d400b2 --- /dev/null +++ b/symphony-bdk-extension-api/src/main/java/com/symphony/bdk/extension/BdkExtensionServiceProvider.java @@ -0,0 +1,23 @@ +package com.symphony.bdk.extension; + +import org.apiguardian.api.API; + +/** + * {@code BdkExtensionServiceProvider} defines the API of {@link BdkExtension} that wish to provide additional services + * to BDK developers. + * + *

Extensions that implement {@code BdkExtensionServiceProvider} must also implement {@link BdkExtension}. + * + * @param Type of the service. + * @see BdkExtension + */ +@API(status = API.Status.EXPERIMENTAL) +public interface BdkExtensionServiceProvider { + + /** + * Returns the extension service instance. + * + * @return extension service instance. + */ + S getService(); +} diff --git a/symphony-bdk-extensions/build.gradle b/symphony-bdk-extensions/build.gradle new file mode 100644 index 000000000..96e27f79a --- /dev/null +++ b/symphony-bdk-extensions/build.gradle @@ -0,0 +1,3 @@ +subprojects { + group = 'org.finos.symphony.bdk.ext' +} diff --git a/symphony-bdk-extensions/symphony-group-extension/README.md b/symphony-bdk-extensions/symphony-group-extension/README.md new file mode 100644 index 000000000..3b5a54f40 --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/README.md @@ -0,0 +1,63 @@ +# Symphony Group Extension +The Symphony Group Extension allows the bot developer to manage their groups of users. + +## Prerequisites +This extension requires the **Distribution List Manager** role assigned to your bot _service account_ from the Symphony +Administration Portal. + +If your service account does not have this specific role, any call to the Groups API will return a `ApiRuntimeException` +with status `403` (and error code `SYMPHONY_PROFILE_MANAGER__ENTITLEMENT_NOT_FOUND`). + +> :warning: Changing the role in ACP requires to re-authenticate your bot's service account. + +## How to use +As this is an additional extension, you must explicitly import it along with the required BDK dependencies. +With Maven: +```xml + + + org.finos.symphony.bdk.ext + symphony-group-extension + + +``` +With Gradle: +```groovy +dependencies { + implementation 'org.finos.symphony.bdk.ext:symphony-group-extension' +} +``` + +### With Spring Boot +In Spring Boot, you just need to manually register the `SymphonyGroupService` as a bean: +```java +@Configuration +public class GroupExtensionConfig { + + @Bean + public SymphonyGroupService groupService( + final RetryWithRecoveryBuilder retryBuilder, + final ApiClientFactory apiClientFactory, + final AuthSession session + ) { + return new SymphonyGroupService(retryBuilder, apiClientFactory, session); + } +} +``` +And then use it in your application, for example: +```java +@RestController +public class GroupApi { + + @Autowired + private SymphonyGroupService groupService; + + @GetMapping("/api/v1/groups") + public GroupList getGroups(@RequestParam(defaultValue = "SDL") String type) { + return this.groupService.listGroups(type, Status.ACTIVE, null, null, null, null); + } +} +``` + +---- +:bulb: For more information about BDK extensions, please refer to this [documentation](../../docs/extension.md). diff --git a/symphony-bdk-extensions/symphony-group-extension/build.gradle b/symphony-bdk-extensions/symphony-group-extension/build.gradle new file mode 100644 index 000000000..ad520077e --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'bdk.java-common-conventions' + id 'bdk.java-publish-conventions' + id 'bdk.java-codegen-conventions' + id 'de.undercouch.download' +} + +description = 'Symphony Java BDK - Groups Extension' + +dependencies { + implementation project(':symphony-bdk-core') + implementation project(':symphony-bdk-extension-api') + implementation project(':symphony-bdk-http:symphony-bdk-http-api') + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + implementation 'org.slf4j:slf4j-api' +} + +def baseSpecsUrl = 'https://raw.githubusercontent.com/finos/symphony-api-spec/master/profile-manager' + +task downloadFile(type: Download) { + src([ + "$baseSpecsUrl/profile-manager-api.yaml", + "$baseSpecsUrl/symphony-common-definitions.yaml" + ]) + dest buildDir + onlyIfModified true + useETag true +} + +openApiGenerate { + inputSpec = "$buildDir/profile-manager-api.yaml" + apiPackage = 'com.symphony.bdk.ext.group.gen.api' + modelPackage = 'com.symphony.bdk.ext.group.gen.api.model' +} + +tasks.openApiGenerate.dependsOn tasks.downloadFile diff --git a/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupBdkExtension.java b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupBdkExtension.java new file mode 100644 index 000000000..eadd265cb --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupBdkExtension.java @@ -0,0 +1,55 @@ +package com.symphony.bdk.ext.group; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.extension.BdkApiClientFactoryAware; +import com.symphony.bdk.core.extension.BdkAuthenticationAware; +import com.symphony.bdk.core.extension.BdkRetryBuilderAware; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.extern.slf4j.Slf4j; +import org.apiguardian.api.API; + +@Slf4j +@API(status = API.Status.EXPERIMENTAL, since = "20.13") +public class SymphonyGroupBdkExtension implements + BdkExtension, + BdkExtensionServiceProvider, + BdkApiClientFactoryAware, + BdkAuthenticationAware, + BdkRetryBuilderAware +{ + + private RetryWithRecoveryBuilder retryBuilder; + private ApiClientFactory apiClientFactory; + private AuthSession session; + + private SymphonyGroupService groupService; + + @Override + public void setApiClientFactory(ApiClientFactory apiClientFactory) { + this.apiClientFactory = apiClientFactory; + } + + @Override + public void setAuthSession(AuthSession session) { + this.session = session; + } + + @Override + public void setRetryBuilder(RetryWithRecoveryBuilder retryBuilder) { + this.retryBuilder = retryBuilder; + } + + @Override + public SymphonyGroupService getService() { + + if (this.groupService == null) { + this.groupService = new SymphonyGroupService(this.retryBuilder, this.apiClientFactory, this.session); + } + + return this.groupService; + } +} diff --git a/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupService.java b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupService.java new file mode 100644 index 000000000..81a9551f1 --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/SymphonyGroupService.java @@ -0,0 +1,110 @@ +package com.symphony.bdk.ext.group; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.retry.RetryWithRecovery; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.core.retry.function.SupplierWithApiException; +import com.symphony.bdk.ext.group.auth.OAuth; +import com.symphony.bdk.ext.group.auth.OAuthSession; +import com.symphony.bdk.ext.group.gen.api.GroupApi; +import com.symphony.bdk.ext.group.gen.api.TypeApi; +import com.symphony.bdk.ext.group.gen.api.model.AddMember; +import com.symphony.bdk.ext.group.gen.api.model.CreateGroup; +import com.symphony.bdk.ext.group.gen.api.model.GroupList; +import com.symphony.bdk.ext.group.gen.api.model.ReadGroup; +import com.symphony.bdk.ext.group.gen.api.model.SortOrder; +import com.symphony.bdk.ext.group.gen.api.model.Status; +import com.symphony.bdk.ext.group.gen.api.model.Type; +import com.symphony.bdk.ext.group.gen.api.model.TypeList; +import com.symphony.bdk.ext.group.gen.api.model.UpdateGroup; +import com.symphony.bdk.ext.group.gen.api.model.UploadAvatar; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.http.api.ApiClient; +import com.symphony.bdk.http.api.ApiException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SymphonyGroupService implements BdkExtensionService { + + private final RetryWithRecoveryBuilder retryBuilder; + private final TypeApi typeApi; + private final GroupApi groupApi; + + public SymphonyGroupService(RetryWithRecoveryBuilder retryBuilder, ApiClientFactory apiClientFactory, AuthSession session) { + + // oAuthSession does not need to be cached, it will be refreshed everytime an API call returns 401 + final OAuthSession oAuthSession = new OAuthSession(apiClientFactory.getLoginClient(), session, retryBuilder); + oAuthSession.refresh(); + + this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder) + .recoveryStrategy(ApiException::isUnauthorized, oAuthSession::refresh); + + final ApiClient client = apiClientFactory.getPodClient("/profile-manager"); + final OAuth auth = new OAuth(oAuthSession::getBearerToken); + client.getAuthentications().put("bearerAuth", auth); + + this.groupApi = new GroupApi(client); + this.typeApi = new TypeApi(client); + } + + public Type getType(@Nonnull String typeId) { + return this.executeAndRetry("groupExt.listTypes", + () -> this.typeApi.getType("", typeId) + ); + } + + public TypeList listTypes(@Nullable Status status, @Nullable String before, @Nullable String after, + @Nullable Integer limit, @Nullable SortOrder sortOrder) { + return this.executeAndRetry("groupExt.listTypes", + () -> this.typeApi.listTypes("", status, before, after, limit, sortOrder) + ); + } + + public ReadGroup insertGroup(@Nonnull final CreateGroup group) { + return this.executeAndRetry("groupExt.insertGroup", + () -> this.groupApi.insertGroup("", group) + ); + } + + public ReadGroup updateGroup(@Nonnull String ifMatch, @Nonnull String groupId, @Nonnull UpdateGroup updateGroup) { + return this.executeAndRetry("groupExt.updateGroup", + () -> this.groupApi.updateGroup("", ifMatch, groupId, updateGroup) + ); + } + + public ReadGroup updateAvatar(@Nonnull String groupId, @Nonnull UploadAvatar uploadAvatar) { + return this.executeAndRetry("groupExt.updateAvatar", + () -> this.groupApi.updateAvatar("", groupId, uploadAvatar) + ); + } + + public ReadGroup getGroup(@Nonnull String groupId) { + return this.executeAndRetry("groupExt.getGroup", + () -> this.groupApi.getGroup("", groupId) + ); + } + + public GroupList listGroups(@Nonnull String typeId, @Nullable Status status, @Nullable String before, + @Nullable String after, @Nullable Integer limit, @Nullable SortOrder sortOrder) { + return this.executeAndRetry("groupExt.listGroups", + () -> this.groupApi.listGroups("", typeId, status, before, after, limit, sortOrder) + ); + } + + public ReadGroup addMemberToGroup(@Nonnull String groupId, @Nonnull AddMember addMember) { + return this.executeAndRetry("groupExt.addMemberToGroup", + () -> this.groupApi.addMemberToGroup("", groupId, addMember) + ); + } + + private T executeAndRetry(String name, SupplierWithApiException supplier) { + return RetryWithRecovery.executeAndRetry( + this.retryBuilder, + name, + this.groupApi.getApiClient().getBasePath(), + supplier + ); + } +} diff --git a/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuth.java b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuth.java new file mode 100644 index 000000000..ca4f2f8fe --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuth.java @@ -0,0 +1,21 @@ +package com.symphony.bdk.ext.group.auth; + +import com.symphony.bdk.http.api.auth.Authentication; + +import lombok.RequiredArgsConstructor; +import org.apiguardian.api.API; + +import java.util.Map; +import java.util.function.Supplier; + +@RequiredArgsConstructor +@API(status = API.Status.INTERNAL) +public class OAuth implements Authentication { + + private final Supplier bearerTokenSupplier; + + @Override + public void apply(final Map headerParams) { + headerParams.put("Authorization", "Bearer " + this.bearerTokenSupplier.get()); + } +} diff --git a/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuthSession.java b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuthSession.java new file mode 100644 index 000000000..8a2a9938e --- /dev/null +++ b/symphony-bdk-extensions/symphony-group-extension/src/main/java/com/symphony/bdk/ext/group/auth/OAuthSession.java @@ -0,0 +1,82 @@ +package com.symphony.bdk.ext.group.auth; + +import static com.symphony.bdk.http.api.Pair.pair; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.retry.RetryWithRecovery; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.http.api.ApiClient; +import com.symphony.bdk.http.api.ApiException; +import com.symphony.bdk.http.api.ApiResponse; +import com.symphony.bdk.http.api.util.TypeReference; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import org.apiguardian.api.API; + +import javax.annotation.Nonnull; + +@API(status = API.Status.INTERNAL) +public class OAuthSession { + + private final ApiClient loginClient; + private final AuthSession session; + private final RetryWithRecoveryBuilder retryBuilder; + + @Getter + private String bearerToken; + + public OAuthSession( + @Nonnull ApiClient loginClient, + @Nonnull AuthSession session, + @Nonnull RetryWithRecoveryBuilder retryBuilder + ) { + this.loginClient = loginClient; + this.session = session; + this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder) + .recoveryStrategy(ApiException::isUnauthorized, session::refresh);; + } + + /** + * Refreshes internal Bearer authentication token from bot's sessionToken. + * + *

Note that this method uses the retry strategy to refresh sessionToken if it has expired. + */ + public void refresh() { + this.bearerToken = RetryWithRecovery.executeAndRetry( + this.retryBuilder, + "groupExt.auth", + this.loginClient.getBasePath(), + this::doRefresh + ); + } + + private String doRefresh() throws ApiException { + + final ApiResponse response = this.loginClient.invokeAPI( + "/idm/tokens", + "POST", + singletonList(pair("scope", "profile-manager")), + null, + singletonMap("sessionToken", this.session.getSessionToken()), + null, + null, + "application/json", + "application/json", + null, + new TypeReference() {} + ); + + return response.getData().getToken(); + } + + @Getter @Setter + private static class TokenResponse { + + @JsonProperty("access_token") + private String token; + } +} diff --git a/symphony-bdk-http/symphony-bdk-http-api/src/main/java/com/symphony/bdk/http/api/auth/Authentication.java b/symphony-bdk-http/symphony-bdk-http-api/src/main/java/com/symphony/bdk/http/api/auth/Authentication.java index 341166899..d6416455e 100644 --- a/symphony-bdk-http/symphony-bdk-http-api/src/main/java/com/symphony/bdk/http/api/auth/Authentication.java +++ b/symphony-bdk-http/symphony-bdk-http-api/src/main/java/com/symphony/bdk/http/api/auth/Authentication.java @@ -2,11 +2,9 @@ import com.symphony.bdk.http.api.ApiClient; import com.symphony.bdk.http.api.ApiException; -import com.symphony.bdk.http.api.Pair; import org.apiguardian.api.API; -import java.util.List; import java.util.Map; /** @@ -18,7 +16,7 @@ public interface Authentication { /** - * Apply authentication settings to header and query params. + * Apply authentication settings to header params. * * @param headerParams Map of header parameters */ diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/SymphonyBdkAutoConfiguration.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/SymphonyBdkAutoConfiguration.java index aeba3deda..e627b1d36 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/SymphonyBdkAutoConfiguration.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/SymphonyBdkAutoConfiguration.java @@ -4,7 +4,9 @@ import com.symphony.bdk.spring.config.BdkApiClientsConfig; import com.symphony.bdk.spring.config.BdkCoreConfig; import com.symphony.bdk.spring.config.BdkDatafeedConfig; +import com.symphony.bdk.spring.config.BdkExtensionConfig; import com.symphony.bdk.spring.config.BdkOboServiceConfig; +import com.symphony.bdk.spring.config.BdkRetryConfig; import com.symphony.bdk.spring.config.BdkServiceConfig; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -15,11 +17,13 @@ */ @Import({ BdkCoreConfig.class, + BdkRetryConfig.class, BdkApiClientsConfig.class, BdkDatafeedConfig.class, BdkServiceConfig.class, BdkOboServiceConfig.class, - BdkActivityConfig.class + BdkActivityConfig.class, + BdkExtensionConfig.class }) @EnableConfigurationProperties(SymphonyBdkCoreProperties.class) public class SymphonyBdkAutoConfiguration {} diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkExtensionConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkExtensionConfig.java new file mode 100644 index 000000000..d2ddcd637 --- /dev/null +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkExtensionConfig.java @@ -0,0 +1,70 @@ +package com.symphony.bdk.spring.config; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.client.ApiClientFactory; +import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.core.extension.ExtensionService; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.extension.BdkExtension; +import com.symphony.bdk.extension.BdkExtensionService; +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import lombok.extern.slf4j.Slf4j; +import org.apiguardian.api.API; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.DependsOn; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Slf4j +@API(status = API.Status.EXPERIMENTAL) +public class BdkExtensionConfig { + + @Bean + @ConditionalOnMissingBean(ExtensionService.class) + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public ExtensionService extensionService( + final RetryWithRecoveryBuilder retryWithRecoveryBuilder, + final ApiClientFactory apiClientFactory, + final Optional botSession, + final BdkConfig config, + final List extensions + ) { + + final ExtensionService extensionService = new ExtensionService( + apiClientFactory, + botSession.orElse(null), + retryWithRecoveryBuilder, + config + ); + + if (!extensions.isEmpty()) { + log.debug("{} extension(s) found from application context. The following extension(s) will be registered:", extensions.size()); + extensions.forEach(e -> log.debug("- {}", e.getClass().getCanonicalName())); + extensions.forEach(extensionService::register); + } + + return extensionService; + } + + @Bean + @DependsOn("extensionService") + public List bdkExtensionServices(final List extensions, final ConfigurableListableBeanFactory beanFactory) { + final List services = new ArrayList<>(extensions.size()); + + for (BdkExtension extension : extensions) { + if (extension instanceof BdkExtensionServiceProvider) { + final BdkExtensionService serviceBean = ((BdkExtensionServiceProvider) extension).getService(); + beanFactory.registerSingleton(serviceBean.getClass().getCanonicalName(), serviceBean); + services.add(serviceBean); + log.info("Extension service bean <{}> successfully registered in application context", serviceBean.getClass().getCanonicalName()); + } + } + + return services; + } +} diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkRetryConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkRetryConfig.java new file mode 100644 index 000000000..e4308d1bd --- /dev/null +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkRetryConfig.java @@ -0,0 +1,18 @@ +package com.symphony.bdk.spring.config; + +import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; + +import org.apiguardian.api.API; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +@API(status = API.Status.STABLE) +public class BdkRetryConfig { + + @Bean + @ConditionalOnMissingBean + public RetryWithRecoveryBuilder retryWithRecoveryBuilder(BdkConfig config) { + return new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry()); + } +} diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java index 8717e8ac5..3e2d5900e 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java @@ -8,6 +8,7 @@ import com.symphony.bdk.core.auth.OboAuthenticator; import com.symphony.bdk.core.auth.exception.AuthInitializationException; import com.symphony.bdk.core.client.loadbalancing.DatafeedLoadBalancedApiClient; +import com.symphony.bdk.core.extension.ExtensionService; import com.symphony.bdk.core.service.datafeed.DatafeedLoop; import com.symphony.bdk.gen.api.SystemApi; import com.symphony.bdk.http.api.ApiClient; @@ -15,6 +16,8 @@ import com.symphony.bdk.spring.config.BdkActivityConfig; import com.symphony.bdk.spring.config.BdkOboServiceConfig; import com.symphony.bdk.spring.config.BdkServiceConfig; +import com.symphony.bdk.spring.extension.TestExtension; +import com.symphony.bdk.spring.extension.TestExtensionService; import com.symphony.bdk.spring.service.DatafeedAsyncLauncherService; import org.junit.jupiter.api.Test; @@ -43,6 +46,7 @@ void shouldLoadContextWithSuccess() { "bdk.bot.username=tibot", "bdk.bot.privateKey.path=classpath:/privatekey.pem" ) + .withBean(TestExtension.class) .withUserConfiguration(SymphonyBdkMockedConfiguration.class) .withConfiguration(AutoConfigurations.of(SymphonyBdkAutoConfiguration.class)); @@ -58,6 +62,12 @@ void shouldLoadContextWithSuccess() { //verify that bean for OBO authentication has not been injected assertThat(context).doesNotHaveBean("oboAuthenticator"); + + // verify extension service + assertThat(context).hasSingleBean(ExtensionService.class); + assertThat(context).hasSingleBean(TestExtension.class); + assertThat(context).hasSingleBean(TestExtensionService.class); + assertThat(context.getBean(ExtensionService.class).service(TestExtension.class)).isEqualTo(context.getBean(TestExtensionService.class)); }); } diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtension.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtension.java new file mode 100644 index 000000000..a6d00c335 --- /dev/null +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtension.java @@ -0,0 +1,18 @@ +package com.symphony.bdk.spring.extension; + +import com.symphony.bdk.extension.BdkExtension; + +import com.symphony.bdk.extension.BdkExtensionServiceProvider; + +import org.springframework.stereotype.Component; + +@Component +public class TestExtension implements BdkExtension, BdkExtensionServiceProvider { + + private final TestExtensionService service = new TestExtensionService(); + + @Override + public TestExtensionService getService() { + return this.service; + } +} diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtensionService.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtensionService.java new file mode 100644 index 000000000..0bfdf94d7 --- /dev/null +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/extension/TestExtensionService.java @@ -0,0 +1,6 @@ +package com.symphony.bdk.spring.extension; + +import com.symphony.bdk.extension.BdkExtensionService; + +public class TestExtensionService implements BdkExtensionService { +}