Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension mechanism / Groups Management APIs #620

Merged
merged 32 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d17097f
Groups API support as an extension with an example, initial commit
thibauult Jan 5, 2022
01513a6
Merge remote-tracking branch 'upstream/main' into feature/extension-m…
thibauult Jan 5, 2022
1554f92
Extension service provider, extension lifecycle aware, preparing core…
thibauult Jan 6, 2022
5911a13
Moved extension API to a separate module, renamed extension parent mo…
thibauult Jan 6, 2022
81743ff
Moved BDK config to a separate module
thibauult Jan 6, 2022
1435f54
Moved BdkConfigAware to symphony-bdk-core-config module
thibauult Jan 6, 2022
38ce068
Added Javadoc and unit tests
thibauult Jan 7, 2022
da63cc4
Added Spring Boot support for extensions
thibauult Jan 7, 2022
99c57b5
Added Spring Boot support testing for extensions
thibauult Jan 7, 2022
2369c92
Removed symphony-bdk-core-config -> symphony-bdk-config
thibauult Jan 7, 2022
903fbf6
Removed symphony-bdk-core-extension-api -> symphony-bdk-extension-api
thibauult Jan 7, 2022
b0d3d78
Renamed extensions related modules
thibauult Jan 7, 2022
8ea755f
Manage Bearer Token refreshing in Groups extension, added extension d…
thibauult Jan 10, 2022
17da4cd
Extension model documentation - initial commit
thibauult Jan 10, 2022
a5b7e57
Share Gradle generator task configuration and dependencies
symphony-youri Jan 11, 2022
b149230
Fixed @symphony-elias's review comments
thibauult Jan 11, 2022
3f75bfd
Merge remote-tracking branch 'origin/feature/extension-mechanism' int…
thibauult Jan 11, 2022
526369b
Removed profile-manager specs from project resources
thibauult Jan 11, 2022
1def99c
Simplified documentation related to extension's service loading with …
thibauult Jan 11, 2022
f01797a
Automatic injection of an extension service in the Spring application…
thibauult Jan 12, 2022
76418b8
Completed extension.md documentation
thibauult Jan 12, 2022
eea68f4
Fixed typo in extension.md
thibauult Jan 13, 2022
7c5237d
Scan extension services
thibauult Jan 14, 2022
b968e41
Enabled allowBeanDefinitionOverriding in SymphonyBdkAutoConfiguration…
thibauult Jan 14, 2022
6f9ece5
Added Gradle build info
thibauult Jan 14, 2022
e919502
gw clean build
thibauult Jan 14, 2022
46af58d
Merge remote-tracking branch 'upstream/main' into feature/extension-m…
thibauult Jan 14, 2022
c1c7b8b
Fixed Spring Boot tests
thibauult Jan 14, 2022
d7f52c5
Extension service finally working with Spring Boot. Service is dynami…
thibauult Jan 14, 2022
0d9e4f6
Merge remote-tracking branch 'upstream/main' into feature/extension-m…
thibauult Jan 14, 2022
1127275
Post-merge adaptations
thibauult Jan 14, 2022
c046f1c
Updated current main version to 2.6.0-SNAPSHOT (next release version)
thibauult Jan 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
37 changes: 37 additions & 0 deletions buildSrc/src/main/groovy/bdk.java-codegen-conventions.gradle
Original file line number Diff line number Diff line change
@@ -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"
]
}
181 changes: 181 additions & 0 deletions docs/extension.md
Original file line number Diff line number Diff line change
@@ -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
thibauult marked this conversation as resolved.
Show resolved Hide resolved
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<MyBdkExtensionService> {

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do it with https://www.baeldung.com/spring-boot-custom-auto-configuration too
But that would probably forces us to have a spring boot group extension module

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeaaaah 839c08d

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪄 🐰 🎩

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we put the lazy on the bean instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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;
}
}
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

4 changes: 4 additions & 0 deletions symphony-bdk-bom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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'
Expand Down
4 changes: 4 additions & 0 deletions symphony-bdk-config/README.md
Original file line number Diff line number Diff line change
@@ -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).

27 changes: 27 additions & 0 deletions symphony-bdk-config/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Loading