Skip to content

Commit

Permalink
Extension mechanism with Groups Management APIs (#620)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thibauult authored Jan 14, 2022
1 parent b77d36a commit e4c7ccc
Show file tree
Hide file tree
Showing 127 changed files with 1,882 additions and 73 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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/'
Expand Down
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
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
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;
}
}
```
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
Loading

0 comments on commit e4c7ccc

Please sign in to comment.