diff --git a/openspec/changes/archive/2026-10-21-client-data-service/.openspec.yaml b/openspec/changes/archive/2026-10-21-client-data-service/.openspec.yaml new file mode 100644 index 000000000..01660bab0 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-client-data-service/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-08 diff --git a/openspec/changes/archive/2026-10-21-client-data-service/design.md b/openspec/changes/archive/2026-10-21-client-data-service/design.md new file mode 100644 index 000000000..926f9bdea --- /dev/null +++ b/openspec/changes/archive/2026-10-21-client-data-service/design.md @@ -0,0 +1,28 @@ +## Context + +The `GET /v5/users/current/clientdata` endpoint returns personal client data for the authenticated user, aggregated across custom folders, multi-chat workspaces, and distribution lists for application `symphonyPrime`. + +This design introduces a first-class `ClientDataService` and its OBO counterpart `OboClientDataService` in `symphony-bdk-core`, wired into `ServiceFactory`, `SymphonyBdk`, `OboServices`, and Spring Boot auto-configurations. + +## Goals / Non-Goals + +**Goals:** +- Provide a first-class service for retrieving client data (`ClientDataResponse`), folders, workspaces, and distribution lists. +- Support both bot service account sessions and On-Behalf-Of (OBO) user sessions. +- Provide automatic retry with recovery on 401 unauthorized (token refresh) via `RetryWithRecoveryBuilder`. +- Provide Spring Boot starter auto-configurations for bot and OBO modes. +- Align with upstream FINOS `symphony-api-spec` updates (`MessagesApi` replacing `StreamContextApi`). + +**Non-Goals:** +- Modifying or updating client data (endpoint is read-only). +- Creating or managing distribution list recipients. + +## Decisions + +**D1 — Package Structure.** The service is placed in its own dedicated package, `com.symphony.bdk.core.service.clientdata`, alongside other functional service packages (`stream`, `message`, `user`, etc.). It includes `package-info.java` annotated with `@NullMarked`. + +**D2 — Service Interface and Ergonomics.** Following BDK conventions (e.g., `UserService` and `OboUserService`), `ClientDataService` implements `OboClientDataService` and `OboService`. In addition to the full aggregated `getClientData()` method, convenience methods (`getUserFolders()`, `getUserWorkspaces()`, `getUserDistributionLists()`) extract sub-components directly. + +**D3 — OBO Support.** Calling `.obo(AuthSession)` on `ClientDataService` creates a new instance scoped to the user session, while maintaining the same retry policies. `OboServices.clientData()` provides entry-point access. + +**D4 — Upstream MessagesApi Alignment.** OpenAPI specification commit `a0b152881a0405b7510cfc7e3a9698e8cb8c566c` on FINOS includes PR #238, which changed the OpenAPI tag for `/v1/stream/{sid}/context` from `StreamContext` to `Messages`. Consequently, `StreamContextApi` was removed by OpenAPI generator and its operation consolidated into `MessagesApi`. `StreamService`, `ServiceFactory`, and Spring starter configurations are updated to use `MessagesApi` directly. diff --git a/openspec/changes/archive/2026-10-21-client-data-service/proposal.md b/openspec/changes/archive/2026-10-21-client-data-service/proposal.md new file mode 100644 index 000000000..6e6fdfe51 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-client-data-service/proposal.md @@ -0,0 +1,53 @@ +## Why + +Symphony users organize personal workflows and conversation spaces in the client through custom folders (grouping conversation streams), multi-chat workspaces, and distribution lists. Previously, the BDK Java SDK had no service to access this client-side organization data, requiring bots or extension applications to either manage state externally or call underlying REST endpoints without BDK retry, auth session, or pagination support. + +With the addition of the `GET /v5/users/current/clientdata` endpoint in SBE Users and its formal specification in `symphony-api-spec`, BDK Java needs a first-class service to expose this data cleanly to bots and OBO user sessions. + +## What Changes + +- **New package `com.symphony.bdk.core.service.clientdata`** containing: + - `ClientDataService`: Service implementing `OboClientDataService` and `OboService` with automatic retry and 401 session token recovery. + - `OboClientDataService`: Service interface exposing OBO-enabled client data methods. + - `package-info.java`: Annotated with `@NullMarked`. +- **Client Data Methods**: + - `getClientData()`: Retrieves aggregated client data (`ClientDataResponse`) containing folders, workspaces, and distribution lists for application `symphonyPrime`. + - `getUserFolders()`: Convenience accessor returning list of `ClientDataStructure` folders. + - `getUserWorkspaces()`: Convenience accessor returning list of `ClientDataStructure` workspaces. + - `getUserDistributionLists()`: Convenience accessor returning list of `DistributionLists`. +- **BDK Entry Point Access**: + - `SymphonyBdk.clientData()` and `SymphonyBdk.getClientDataService()` accessors. + - `OboServices.clientData()` and `OboServices.getClientDataService()` accessors. + - `ServiceFactory.getClientDataService()` factory method. +- **Spring Boot Starter Configuration**: + - `@Bean clientDataService(...)` auto-configured in `BdkServiceConfig`. + - `@Bean oboClientDataService(...)` auto-configured in `BdkOboServiceConfig`. +- **OpenAPI Specification Bump & Upstream Alignment**: + - Bumped `apiBaseUrl` in `symphony-bdk-core/build.gradle` to FINOS `symphony-api-spec` commit `a0b152881a0405b7510cfc7e3a9698e8cb8c566c`. + - Aligned with upstream PR #238 (`BE-10206`): migrated `StreamService`, `ServiceFactory`, and Spring starter configurations from `StreamContextApi` to `MessagesApi` following the `/v1/stream/{sid}/context` OpenAPI tag update. +- **Testing**: + - Comprehensive unit test coverage in `ClientDataServiceTest` covering successful responses, empty responses, recipient details, OBO execution, and unauthorized state exceptions. + - Updated `StreamServiceTest`, `ServiceFactoryTest`, and `OboServicesTest`. + +## Capabilities + +### New Capabilities +- `client-data-service`: Access to the authenticated user's personal client data structures (custom folders, workspaces, and distribution lists) via bot and OBO sessions. + +### Modified Capabilities +*(none)* + +## Impact + +**Code**: +- New service package `com.symphony.bdk.core.service.clientdata` +- `SymphonyBdk.java`, `OboServices.java`, `ServiceFactory.java`: added client data accessors +- `StreamService.java`, `ServiceFactory.java`: updated to use `MessagesApi` +- `BdkServiceConfig.java`, `BdkOboServiceConfig.java`, `BdkApiClientsConfig.java`: Spring starter bean configurations updated +- `symphony-bdk-core/build.gradle`: bumped `apiBaseUrl` + +**Dependencies**: +- No new external runtime dependencies. + +**Docs**: +- Updated documentation and capability specs. diff --git a/openspec/changes/archive/2026-10-21-client-data-service/specs/client-data-service/spec.md b/openspec/changes/archive/2026-10-21-client-data-service/specs/client-data-service/spec.md new file mode 100644 index 000000000..e07090a5d --- /dev/null +++ b/openspec/changes/archive/2026-10-21-client-data-service/specs/client-data-service/spec.md @@ -0,0 +1,44 @@ +## ADDED Requirements + +### Requirement: Client Data Retrieval +`ClientDataService` and `OboClientDataService` SHALL retrieve the authenticated user's aggregated client data from the `GET /v5/users/current/clientdata` endpoint, returning a `ClientDataResponse` containing the user's folders, workspaces, and distribution lists for application `symphonyPrime`. + +#### Scenario: Successful client data retrieval +- **WHEN** `getClientData()` is called with an active bot or OBO session +- **THEN** the client data endpoint is queried and a populated `ClientDataResponse` is returned + +#### Scenario: Unauthenticated invocation +- **WHEN** `getClientData()` is called on an unauthenticated service instance without an active `AuthSession` +- **THEN** an `IllegalStateException` is thrown before any network call is initiated + +### Requirement: Folders and Workspaces Access +`ClientDataService` and `OboClientDataService` SHALL provide convenience accessors `getUserFolders()` and `getUserWorkspaces()` that return lists of `ClientDataStructure` elements from the aggregated client data. + +#### Scenario: Retrieve user folders +- **WHEN** `getUserFolders()` is called +- **THEN** the list of `ClientDataStructure` folders is returned, each containing `id`, `name`, and conversation `streamIds` + +#### Scenario: Retrieve user workspaces +- **WHEN** `getUserWorkspaces()` is called +- **THEN** the list of `ClientDataStructure` workspaces is returned, each containing `id`, `name`, and multi-chat `streamIds` + +### Requirement: Distribution Lists Access +`ClientDataService` and `OboClientDataService` SHALL provide a convenience accessor `getUserDistributionLists()` returning the list of `DistributionLists` from the aggregated client data, each containing recipients of type user, stream, or folder. + +#### Scenario: Retrieve distribution lists +- **WHEN** `getUserDistributionLists()` is called +- **THEN** the list of `DistributionLists` is returned with recipient IDs, types, user names, and folder stream IDs + +### Requirement: OBO Session Support +`ClientDataService` SHALL implement `OboService` such that calling `.obo(AuthSession)` produces an instance scoped to the specified user session, using the user session token for authentication. + +#### Scenario: OBO execution +- **WHEN** `.obo(oboSession)` is invoked with an OBO auth session +- **THEN** subsequent calls execute against `/v5/users/current/clientdata` using the OBO session token + +### Requirement: BDK Entry Point Availability +`SymphonyBdk` and `OboServices` SHALL expose `clientData()` and `getClientDataService()` accessors returning `ClientDataService` and `OboClientDataService` respectively. `ServiceFactory` SHALL provide `getClientDataService()`. + +#### Scenario: Entry point retrieval +- **WHEN** `bdk.clientData()` or `oboServices.clientData()` is invoked +- **THEN** a fully initialized client data service is returned diff --git a/openspec/changes/archive/2026-10-21-client-data-service/tasks.md b/openspec/changes/archive/2026-10-21-client-data-service/tasks.md new file mode 100644 index 000000000..bf7a87da8 --- /dev/null +++ b/openspec/changes/archive/2026-10-21-client-data-service/tasks.md @@ -0,0 +1,26 @@ +## 1. OpenAPI Specification Bump & Code Generation + +- [x] 1.1 Bump `apiBaseUrl` in `symphony-bdk-core/build.gradle` to FINOS `symphony-api-spec` commit `a0b152881a0405b7510cfc7e3a9698e8cb8c566c` +- [x] 1.2 Generate OpenAPI client and models: `ClientDataApi`, `ClientDataResponse`, `ClientDataStructure`, `DistributionLists`, `DistributionListRecipient` +- [x] 1.3 Align `StreamService` and `ServiceFactory` with `MessagesApi` replacing `StreamContextApi` + +## 2. Core Service Implementation + +- [x] 2.1 Create `com.symphony.bdk.core.service.clientdata.OboClientDataService` interface declaring `getClientData()`, `getUserFolders()`, `getUserWorkspaces()`, `getUserDistributionLists()` +- [x] 2.2 Create `com.symphony.bdk.core.service.clientdata.ClientDataService` implementing `OboClientDataService` and `OboService` with `RetryWithRecoveryBuilder` +- [x] 2.3 Add `getClientDataService()` factory method to `ServiceFactory` +- [x] 2.4 Add `clientData()` and `getClientDataService()` accessors to `SymphonyBdk` +- [x] 2.5 Add `clientData()` and `getClientDataService()` accessors to `OboServices` +- [x] 2.6 Add `package-info.java` annotated with `@NullMarked` + +## 3. Spring Boot Auto-Configuration + +- [x] 3.1 Register `@Bean clientDataService` in `BdkServiceConfig` +- [x] 3.2 Register `@Bean oboClientDataService` in `BdkOboServiceConfig` +- [x] 3.3 Update `BdkApiClientsConfig`, `BdkServiceConfig`, and `BdkOboServiceConfig` for `MessagesApi` + +## 4. Testing & Verification + +- [x] 4.1 Unit tests in `ClientDataServiceTest` covering `getClientData`, `getUserFolders`, `getUserWorkspaces`, `getUserDistributionLists`, empty response, OBO mode, and unauthenticated errors +- [x] 4.2 Update `StreamServiceTest`, `ServiceFactoryTest`, and `OboServicesTest` +- [x] 4.3 Run full Gradle build, checks, and test coverage verification diff --git a/openspec/specs/client-data-service/spec.md b/openspec/specs/client-data-service/spec.md new file mode 100644 index 000000000..6d183b02d --- /dev/null +++ b/openspec/specs/client-data-service/spec.md @@ -0,0 +1,49 @@ +# client-data-service Specification + +## Purpose +Provides access to personal client data (custom folders, multi-chat workspaces, and distribution lists) for authenticated bot and On-Behalf-Of (OBO) user sessions via the `GET /v5/users/current/clientdata` Symphony endpoint. + +## Requirements + +### Requirement: Client Data Retrieval +`ClientDataService` and `OboClientDataService` SHALL retrieve the authenticated user's aggregated client data from the `GET /v5/users/current/clientdata` endpoint, returning a `ClientDataResponse` containing the user's folders, workspaces, and distribution lists for application `symphonyPrime`. + +#### Scenario: Successful client data retrieval +- **WHEN** `getClientData()` is called with an active bot or OBO session +- **THEN** the client data endpoint is queried and a populated `ClientDataResponse` is returned + +#### Scenario: Unauthenticated invocation +- **WHEN** `getClientData()` is called on an unauthenticated service instance without an active `AuthSession` +- **THEN** an `IllegalStateException` is thrown before any network call is initiated + +### Requirement: Folders and Workspaces Access +`ClientDataService` and `OboClientDataService` SHALL provide convenience accessors `getUserFolders()` and `getUserWorkspaces()` that return lists of `ClientDataStructure` elements from the aggregated client data. + +#### Scenario: Retrieve user folders +- **WHEN** `getUserFolders()` is called +- **THEN** the list of `ClientDataStructure` folders is returned, each containing `id`, `name`, and conversation `streamIds` + +#### Scenario: Retrieve user workspaces +- **WHEN** `getUserWorkspaces()` is called +- **THEN** the list of `ClientDataStructure` workspaces is returned, each containing `id`, `name`, and multi-chat `streamIds` + +### Requirement: Distribution Lists Access +`ClientDataService` and `OboClientDataService` SHALL provide a convenience accessor `getUserDistributionLists()` returning the list of `DistributionLists` from the aggregated client data, each containing recipients of type user, stream, or folder. + +#### Scenario: Retrieve distribution lists +- **WHEN** `getUserDistributionLists()` is called +- **THEN** the list of `DistributionLists` is returned with recipient IDs, types, user names, and folder stream IDs + +### Requirement: OBO Session Support +`ClientDataService` SHALL implement `OboService` such that calling `.obo(AuthSession)` produces an instance scoped to the specified user session, using the user session token for authentication. + +#### Scenario: OBO execution +- **WHEN** `.obo(oboSession)` is invoked with an OBO auth session +- **THEN** subsequent calls execute against `/v5/users/current/clientdata` using the OBO session token + +### Requirement: BDK Entry Point Availability +`SymphonyBdk` and `OboServices` SHALL expose `clientData()` and `getClientDataService()` accessors returning `ClientDataService` and `OboClientDataService` respectively. `ServiceFactory` SHALL provide `getClientDataService()`. + +#### Scenario: Entry point retrieval +- **WHEN** `bdk.clientData()` or `oboServices.clientData()` is invoked +- **THEN** a fully initialized client data service is returned diff --git a/symphony-bdk-core/build.gradle b/symphony-bdk-core/build.gradle index 139ce401d..1d857c67c 100644 --- a/symphony-bdk-core/build.gradle +++ b/symphony-bdk-core/build.gradle @@ -80,7 +80,7 @@ dependencies { } // OpenAPI code generation -def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/a8efe8a6193769d0657fc57758918138aa4483bb" +def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/a0b152881a0405b7510cfc7e3a9698e8cb8c566c" def generatedFolder = "${layout.buildDirectory.get()}/generated/openapi" def apisToGenerate = [ Agent: 'agent/agent-api-public-deprecated.yaml', diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/OboServices.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/OboServices.java index 51b4dbf25..7c80d4ce4 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/OboServices.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/OboServices.java @@ -11,6 +11,7 @@ import com.symphony.bdk.core.service.session.OboSessionService; import com.symphony.bdk.core.service.signal.OboSignalService; import com.symphony.bdk.core.service.stream.OboStreamService; +import com.symphony.bdk.core.service.clientdata.OboClientDataService; import com.symphony.bdk.core.service.user.OboUserService; import org.apiguardian.api.API; @@ -25,6 +26,7 @@ public class OboServices { private final OboStreamService oboStreamService; private final OboUserService oboUserService; + private final OboClientDataService oboClientDataService; private final OboMessageService oboMessageService; private final OboPresenceService oboPresenceService; private final OboConnectionService oboConnectionService; @@ -46,6 +48,7 @@ public OboServices( oboStreamService = serviceFactory.getStreamService(); oboUserService = serviceFactory.getUserService(); + oboClientDataService = serviceFactory.getClientDataService(); oboMessageService = serviceFactory.getMessageService(); oboPresenceService = serviceFactory.getPresenceService(); oboConnectionService = serviceFactory.getConnectionService(); @@ -71,6 +74,24 @@ public OboUserService users() { return oboUserService; } + /** + * Get the {@link OboClientDataService} using the provided OBO session in constructor. + * + * @return an {@link OboClientDataService} instance with the provided OBO session. + */ + public OboClientDataService clientData() { + return oboClientDataService; + } + + /** + * Get the {@link OboClientDataService} using the provided OBO session in constructor. + * + * @return an {@link OboClientDataService} instance with the provided OBO session. + */ + public OboClientDataService getClientDataService() { + return oboClientDataService; + } + /** * Get the {@link OboPresenceService} using the provided OBO session in constructor. * diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java index 17f0305bb..94c22876d 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java @@ -26,6 +26,7 @@ import com.symphony.bdk.core.service.session.SessionService; import com.symphony.bdk.core.service.signal.SignalService; import com.symphony.bdk.core.service.stream.StreamService; +import com.symphony.bdk.core.service.clientdata.ClientDataService; import com.symphony.bdk.core.service.user.UserService; import com.symphony.bdk.gen.api.AppEntitlementApi; import com.symphony.bdk.gen.api.ApplicationApi; @@ -45,7 +46,6 @@ import com.symphony.bdk.gen.api.SessionApi; import com.symphony.bdk.gen.api.ShareApi; import com.symphony.bdk.gen.api.SignalsApi; -import com.symphony.bdk.gen.api.StreamContextApi; import com.symphony.bdk.gen.api.StreamsApi; import com.symphony.bdk.gen.api.SystemApi; import com.symphony.bdk.gen.api.UserApi; @@ -75,6 +75,7 @@ class ServiceFactory { private final ApiClient podClient; + private final ApiClient usersClient; private final ApiClient agentClient; private final ApiClient datafeedAgentClient; private final ApiClient datahoseAgentClient; @@ -100,6 +101,7 @@ public ServiceFactory( ) { this.config = config; this.podClient = apiClientFactory.getPodClient(); + this.usersClient = apiClientFactory.getUsersClient(); this.agentClient = apiClientFactory.getAgentClient(); this.datafeedAgentClient = apiClientFactory.getDatafeedAgentClient(); this.datahoseAgentClient = apiClientFactory.getDatahoseAgentClient(); @@ -120,6 +122,11 @@ public ServiceFactory( this.podClient.getAuthentications().put(BEARER_AUTH, new OAuthentication(oAuthSession::getBearerToken)); this.podClient.addEnforcedAuthenticationScheme(BEARER_AUTH); + if (this.usersClient != null) { + this.usersClient.getAuthentications().put(BEARER_AUTH, new OAuthentication(oAuthSession::getBearerToken)); + this.usersClient.addEnforcedAuthenticationScheme(BEARER_AUTH); + } + this.agentClient.getAuthentications().put(BEARER_AUTH, new OAuthentication(oAuthSession::getBearerToken)); this.agentClient.addEnforcedAuthenticationScheme(BEARER_AUTH); @@ -142,6 +149,15 @@ public UserService getUserService() { retryBuilder); } + /** + * Returns a fully initialized {@link ClientDataService}. + * + * @return a new {@link ClientDataService} instance. + */ + public ClientDataService getClientDataService() { + return new ClientDataService(this.usersClient, this.authSession, this.retryBuilder); + } + /** * Returns a fully initialized {@link StreamService}. * @@ -149,7 +165,7 @@ public UserService getUserService() { */ public StreamService getStreamService() { return new StreamService(new StreamsApi(podClient), new RoomMembershipApi(podClient), new ShareApi(agentClient), - new StreamContextApi(agentClient), authSession, retryBuilder); + new MessagesApi(agentClient), authSession, retryBuilder); } public DisclaimerService getDisclaimerService() { diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdk.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdk.java index 4b367dd48..0bcbd6f96 100644 --- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdk.java +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/SymphonyBdk.java @@ -24,6 +24,7 @@ import com.symphony.bdk.core.service.session.SessionService; import com.symphony.bdk.core.service.signal.SignalService; import com.symphony.bdk.core.service.stream.StreamService; +import com.symphony.bdk.core.service.clientdata.ClientDataService; import com.symphony.bdk.core.service.user.UserService; import com.symphony.bdk.core.util.ServiceLookup; import com.symphony.bdk.extension.BdkExtension; @@ -60,6 +61,7 @@ public class SymphonyBdk { private final ActivityRegistry activityRegistry; private final StreamService streamService; private final UserService userService; + private final ClientDataService clientDataService; private final MessageService messageService; private final PresenceService presenceService; private final ConnectionService connectionService; @@ -162,6 +164,7 @@ protected SymphonyBdk( this.sessionService = serviceFactory != null ? serviceFactory.getSessionService() : null; this.userService = serviceFactory != null ? serviceFactory.getUserService() : null; + this.clientDataService = serviceFactory != null ? serviceFactory.getClientDataService() : null; this.streamService = serviceFactory != null ? serviceFactory.getStreamService() : null; this.presenceService = serviceFactory != null ? serviceFactory.getPresenceService() : null; this.connectionService = serviceFactory != null ? serviceFactory.getConnectionService() : null; @@ -243,6 +246,24 @@ public UserService users() { return getOrThrowNoBotConfig(this.userService); } + /** + * Get the {@link ClientDataService} from a Bdk entry point. + * + * @return {@link ClientDataService} instance. + */ + public ClientDataService clientData() { + return getOrThrowNoBotConfig(this.clientDataService); + } + + /** + * Get the {@link ClientDataService} from a Bdk entry point. + * + * @return {@link ClientDataService} instance. + */ + public ClientDataService getClientDataService() { + return this.clientDataService; + } + /** * Get the {@link StreamService} from a Bdk entry point. * diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/ClientDataService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/ClientDataService.java new file mode 100644 index 000000000..8f13ad10b --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/ClientDataService.java @@ -0,0 +1,91 @@ +package com.symphony.bdk.core.service.clientdata; + +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.retry.function.SupplierWithApiException; +import com.symphony.bdk.core.service.OboService; +import com.symphony.bdk.gen.api.ClientDataApi; +import com.symphony.bdk.gen.api.model.ClientDataResponse; +import com.symphony.bdk.gen.api.model.DistributionLists; +import com.symphony.bdk.gen.api.model.ClientDataStructure; +import com.symphony.bdk.http.api.ApiClient; +import com.symphony.bdk.http.api.ApiException; + +import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +/** + * Service to retrieve the authenticated user's client data organization structures + * custom folders, multi-chat workspaces, and distribution lists from the + * {@code GET /v5/users/current/clientdata} endpoint. + */ +@API(status = API.Status.STABLE) +public class ClientDataService implements OboClientDataService, OboService { + + private final ApiClient apiClient; + private final ClientDataApi clientDataApi; + private final @Nullable AuthSession authSession; + private final RetryWithRecoveryBuilder retryBuilder; + + public ClientDataService(ApiClient apiClient, AuthSession authSession, + RetryWithRecoveryBuilder retryBuilder) { + this.apiClient = apiClient; + this.clientDataApi = new ClientDataApi(apiClient); + this.authSession = authSession; + this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder) + .recoveryStrategy(ApiException::isUnauthorized, authSession::refresh); + } + + public ClientDataService(ApiClient apiClient, RetryWithRecoveryBuilder retryBuilder) { + this.apiClient = apiClient; + this.clientDataApi = new ClientDataApi(apiClient); + this.authSession = null; + this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder); + } + + @Override + public OboClientDataService obo(AuthSession oboSession) { + return new ClientDataService(this.apiClient, oboSession, this.retryBuilder); + } + + /** + * {@inheritDoc} + */ + @Override + public ClientDataResponse getClientData() { + return executeAndRetry("getClientData", + () -> this.clientDataApi.getClientData(this.authSession.getSessionToken(), null)); + } + + /** + * {@inheritDoc} + */ + @Override + public List getUserFolders() { + return getClientData().getFolders(); + } + + /** + * {@inheritDoc} + */ + @Override + public List getUserWorkspaces() { + return getClientData().getWorkspaces(); + } + + /** + * {@inheritDoc} + */ + @Override + public List getUserDistributionLists() { + return getClientData().getDistributionLists(); + } + + private T executeAndRetry(String name, SupplierWithApiException supplier) { + checkAuthSession(this.authSession); + return RetryWithRecovery.executeAndRetry(this.retryBuilder, name, this.apiClient.getBasePath(), supplier); + } +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/OboClientDataService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/OboClientDataService.java new file mode 100644 index 000000000..86a80bbde --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/OboClientDataService.java @@ -0,0 +1,49 @@ +package com.symphony.bdk.core.service.clientdata; + +import com.symphony.bdk.gen.api.model.ClientDataResponse; +import com.symphony.bdk.gen.api.model.DistributionLists; +import com.symphony.bdk.gen.api.model.ClientDataStructure; + +import org.apiguardian.api.API; + +import java.util.List; + +/** + * Service interface exposing OBO-enabled endpoints to manage client data. + */ +@API(status = API.Status.STABLE) +public interface OboClientDataService { + + /** + * Retrieves the authenticated user's aggregated client data (folders, workspaces, and + * distribution lists) for the default {@code symphonyPrime} application. + * {@link ClientDataService#getClientData()} + * + * @return a {@link ClientDataResponse} containing the user's client data. + */ + ClientDataResponse getClientData(); + + /** + * Retrieves the authenticated user's custom folders. + * {@link ClientDataService#getUserFolders()} + * + * @return a list of {@link ClientDataStructure} folders. + */ + List getUserFolders(); + + /** + * Retrieves the authenticated user's workspaces. + * {@link ClientDataService#getUserWorkspaces()} + * + * @return a list of {@link ClientDataStructure} workspaces. + */ + List getUserWorkspaces(); + + /** + * Retrieves the authenticated user's distribution lists, each with its recipients. + * {@link ClientDataService#getUserDistributionLists()} + * + * @return a list of {@link DistributionLists} distribution lists. + */ + List getUserDistributionLists(); +} diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/package-info.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/package-info.java new file mode 100644 index 000000000..31e1fb36f --- /dev/null +++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/clientdata/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.symphony.bdk.core.service.clientdata; + +import org.jspecify.annotations.NullMarked; 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 9f9da24e0..95125d647 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 @@ -12,9 +12,9 @@ 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.gen.api.MessagesApi; import com.symphony.bdk.gen.api.RoomMembershipApi; import com.symphony.bdk.gen.api.ShareApi; -import com.symphony.bdk.gen.api.StreamContextApi; import com.symphony.bdk.gen.api.StreamsApi; import com.symphony.bdk.gen.api.model.MemberInfo; import com.symphony.bdk.gen.api.model.RoomDetail; @@ -66,29 +66,29 @@ public class StreamService implements OboStreamService, OboService retryBuilder; public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, ShareApi shareApi, - StreamContextApi streamContextApi, + MessagesApi messagesApi, AuthSession authSession, RetryWithRecoveryBuilder retryBuilder) { this.streamsApi = streamsApi; this.roomMembershipApi = membershipApi; this.shareApi = shareApi; - this.streamContextApi = streamContextApi; + this.messagesApi = messagesApi; this.authSession = authSession; this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder) .recoveryStrategy(ApiException::isUnauthorized, authSession::refresh); } public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, ShareApi shareApi, - StreamContextApi streamContextApi, + MessagesApi messagesApi, RetryWithRecoveryBuilder retryBuilder) { this.streamsApi = streamsApi; this.roomMembershipApi = membershipApi; this.shareApi = shareApi; - this.streamContextApi = streamContextApi; + this.messagesApi = messagesApi; this.authSession = null; this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder); } @@ -98,7 +98,7 @@ public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, Sha */ @Override public OboStreamService obo(AuthSession oboSession) { - return new StreamService(streamsApi, roomMembershipApi, shareApi, streamContextApi, oboSession, retryBuilder); + return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, oboSession, retryBuilder); } /** @@ -505,8 +505,8 @@ public List listRoomMembers(String roomId) { @Override public String getStreamContext(String streamId) { - return executeAndRetry("getStreamContext", streamContextApi.getApiClient().getBasePath(), - () -> streamContextApi.v1StreamContext(toUrlSafeIdIfNeeded(streamId), authSession.getSessionToken(), authSession.getKeyManagerToken())); + return executeAndRetry("getStreamContext", messagesApi.getApiClient().getBasePath(), + () -> messagesApi.v1StreamContext(toUrlSafeIdIfNeeded(streamId), null, null, null, null, authSession.getSessionToken(), authSession.getKeyManagerToken())); } private T executeAndRetry(String name, String address, SupplierWithApiException supplier) { diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/OboServicesTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/OboServicesTest.java index d1596b8ec..b646b803a 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/OboServicesTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/OboServicesTest.java @@ -6,6 +6,7 @@ import com.symphony.bdk.core.auth.AuthSession; import com.symphony.bdk.core.config.model.BdkConfig; +import com.symphony.bdk.core.service.clientdata.OboClientDataService; import com.symphony.bdk.core.service.connection.OboConnectionService; import com.symphony.bdk.core.service.message.OboMessageService; import com.symphony.bdk.core.service.presence.OboPresenceService; @@ -68,4 +69,10 @@ void testOboSessions() { OboSessionService sessionService = oboServices.sessions(); assertNotNull(sessionService); } + + @Test + void testOboClientData() { + OboClientDataService clientDataService = oboServices.clientData(); + assertNotNull(clientDataService); + } } diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java index 4e151e335..1ab7f1545 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java @@ -30,6 +30,7 @@ import com.symphony.bdk.core.service.session.SessionService; import com.symphony.bdk.core.service.signal.SignalService; import com.symphony.bdk.core.service.stream.StreamService; +import com.symphony.bdk.core.service.clientdata.ClientDataService; import com.symphony.bdk.core.service.user.UserService; import com.symphony.bdk.gen.api.model.UserV2; import com.symphony.bdk.http.api.ApiClient; @@ -55,6 +56,7 @@ void setUp() throws BdkConfigException { this.apiClientFactory = mock(ApiClientFactory.class); when(this.apiClientFactory.getPodClient()).thenReturn(mPodClient); + when(this.apiClientFactory.getUsersClient()).thenReturn(mock(ApiClient.class)); when(this.apiClientFactory.getAgentClient()).thenReturn(mock(ApiClient.class)); ApiClient datafeedAgentClient = mock(ApiClient.class); @@ -71,6 +73,12 @@ void getUserServiceTest() { assertNotNull(userService); } + @Test + void getClientDataServiceTest() { + ClientDataService clientDataService = this.serviceFactory.getClientDataService(); + assertNotNull(clientDataService); + } + @Test void getStreamServiceTest() { StreamService streamService = this.serviceFactory.getStreamService(); diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/clientdata/ClientDataServiceTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/clientdata/ClientDataServiceTest.java new file mode 100644 index 000000000..5a2f30ba8 --- /dev/null +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/clientdata/ClientDataServiceTest.java @@ -0,0 +1,157 @@ +package com.symphony.bdk.core.service.clientdata; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.symphony.bdk.core.auth.AuthSession; +import com.symphony.bdk.core.retry.RetryWithRecoveryBuilder; +import com.symphony.bdk.core.test.MockApiClient; +import com.symphony.bdk.gen.api.model.ClientDataResponse; +import com.symphony.bdk.gen.api.model.DistributionLists; +import com.symphony.bdk.gen.api.model.ClientDataStructure; +import com.symphony.bdk.http.api.ApiClient; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientDataServiceTest { + + private static final String SESSION_TOKEN = "test-session-token"; + private static final String CLIENT_DATA_PATH = "/v5/users/current/clientdata"; + + private static final String CLIENT_DATA_JSON = "{\n" + + " \"folders\": [\n" + + " {\"id\": \"folder-27001\", \"name\": \"Other Teams\", \"streamIds\": [\"stream-a\", \"stream-b\"]}\n" + + " ],\n" + + " \"workspaces\": [\n" + + " {\"id\": \"tab-1001\", \"name\": \"SRE Workspace\", \"streamIds\": [\"sre-stream-1\", \"sre-stream-2\"]}\n" + + " ],\n" + + " \"distributionLists\": [\n" + + " {\"id\": \"a97be35a\", \"name\": \"Blast People\", \"recipients\": [" + + " {\"id\": \"user-1\", \"type\": \"user\", \"name\": \"Test Name\"}," + + " {\"id\": \"folder-1\", \"type\": \"folder\", \"streamIds\": [\"stream-a\"]}," + + " {\"id\": \"blast-stream-1\", \"type\": \"stream\"}]}\n" + + " ]\n" + + "}"; + + private MockApiClient mockApiClient; + private AuthSession authSession; + private ClientDataService service; + + @BeforeEach + void setUp() { + this.mockApiClient = new MockApiClient(); + this.authSession = mock(AuthSession.class); + when(this.authSession.getSessionToken()).thenReturn(SESSION_TOKEN); + + ApiClient usersClient = this.mockApiClient.getApiClient(""); + this.service = new ClientDataService(usersClient, this.authSession, new RetryWithRecoveryBuilder<>()); + } + + @Test + void testGetClientDataSuccess() { + this.mockApiClient.onGet(CLIENT_DATA_PATH, CLIENT_DATA_JSON); + + ClientDataResponse org = this.service.getClientData(); + assertNotNull(org); + + assertEquals(1, org.getFolders().size()); + ClientDataStructure dir = org.getFolders().get(0); + assertEquals("folder-27001", dir.getId()); + assertEquals("Other Teams", dir.getName()); + assertEquals(List.of("stream-a", "stream-b"), dir.getStreamIds()); + + assertEquals(1, org.getWorkspaces().size()); + ClientDataStructure ws = org.getWorkspaces().get(0); + assertEquals("tab-1001", ws.getId()); + assertEquals("SRE Workspace", ws.getName()); + assertEquals(List.of("sre-stream-1", "sre-stream-2"), ws.getStreamIds()); + + assertEquals(1, org.getDistributionLists().size()); + DistributionLists bl = org.getDistributionLists().get(0); + assertEquals("a97be35a", bl.getId()); + assertEquals("Blast People", bl.getName()); + assertEquals(3, bl.getRecipients().size()); + assertEquals("user-1", bl.getRecipients().get(0).getId()); + assertEquals("user", bl.getRecipients().get(0).getType()); + assertEquals("Test Name", bl.getRecipients().get(0).getName()); + assertEquals("folder-1", bl.getRecipients().get(1).getId()); + assertEquals("folder", bl.getRecipients().get(1).getType()); + assertEquals(List.of("stream-a"), bl.getRecipients().get(1).getStreamIds()); + assertEquals("blast-stream-1", bl.getRecipients().get(2).getId()); + assertEquals("stream", bl.getRecipients().get(2).getType()); + } + + @Test + void testGetUserFolders() { + this.mockApiClient.onGet(CLIENT_DATA_PATH, CLIENT_DATA_JSON); + + List folders = this.service.getUserFolders(); + assertEquals(1, folders.size()); + assertEquals("folder-27001", folders.get(0).getId()); + assertEquals(List.of("stream-a", "stream-b"), folders.get(0).getStreamIds()); + } + + @Test + void testGetUserWorkspaces() { + this.mockApiClient.onGet(CLIENT_DATA_PATH, CLIENT_DATA_JSON); + + List workspaces = this.service.getUserWorkspaces(); + assertEquals(1, workspaces.size()); + assertEquals("tab-1001", workspaces.get(0).getId()); + assertEquals("SRE Workspace", workspaces.get(0).getName()); + } + + @Test + void testGetUserDistributionLists() { + this.mockApiClient.onGet(CLIENT_DATA_PATH, CLIENT_DATA_JSON); + + List distributionLists = this.service.getUserDistributionLists(); + assertEquals(1, distributionLists.size()); + assertEquals("a97be35a", distributionLists.get(0).getId()); + assertEquals("Blast People", distributionLists.get(0).getName()); + assertEquals(3, distributionLists.get(0).getRecipients().size()); + assertEquals("stream", distributionLists.get(0).getRecipients().get(2).getType()); + } + + @Test + void testEmptyResponseReturnsEmptyLists() { + this.mockApiClient.onGet(CLIENT_DATA_PATH, "{}"); + + ClientDataResponse org = this.service.getClientData(); + assertNotNull(org); + assertTrue(org.getFolders().isEmpty()); + assertTrue(org.getWorkspaces().isEmpty()); + assertTrue(org.getDistributionLists().isEmpty()); + } + + @Test + void testOboModeExecution() { + AuthSession oboSession = mock(AuthSession.class); + when(oboSession.getSessionToken()).thenReturn("obo-token"); + + OboClientDataService oboService = this.service.obo(oboSession); + assertNotNull(oboService); + + this.mockApiClient.onGet(CLIENT_DATA_PATH, CLIENT_DATA_JSON); + List distributionLists = oboService.getUserDistributionLists(); + assertEquals(1, distributionLists.size()); + assertEquals("a97be35a", distributionLists.get(0).getId()); + assertEquals(3, distributionLists.get(0).getRecipients().size()); + } + + @Test + void testMissingAuthSessionThrowsIllegalStateException() { + ApiClient usersClient = this.mockApiClient.getApiClient(""); + ClientDataService unauthenticatedService = + new ClientDataService(usersClient, new RetryWithRecoveryBuilder<>()); + + assertThrows(IllegalStateException.class, unauthenticatedService::getClientData); + } +} diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java index dc18ec929..f96a568d0 100644 --- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java +++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java @@ -21,7 +21,7 @@ import com.symphony.bdk.core.test.MockApiClient; import com.symphony.bdk.gen.api.RoomMembershipApi; import com.symphony.bdk.gen.api.ShareApi; -import com.symphony.bdk.gen.api.StreamContextApi; +import com.symphony.bdk.gen.api.MessagesApi; import com.symphony.bdk.gen.api.StreamsApi; import com.symphony.bdk.gen.api.model.MemberInfo; import com.symphony.bdk.gen.api.model.RoomDetail; @@ -86,7 +86,7 @@ public class StreamServiceTest { private RoomMembershipApi spyRoomMembershipApi; private AuthSession authSession; private StreamsApi streamsApi; - private StreamContextApi streamContextApi; + private MessagesApi messagesApi; private ShareApi shareApi; @BeforeEach @@ -99,8 +99,8 @@ void setUp() { this.spyRoomMembershipApi = spy(new RoomMembershipApi(podClient)); this.streamsApi = spy(new StreamsApi(podClient)); this.shareApi = new ShareApi(agentClient); - this.streamContextApi = new StreamContextApi(agentClient); - this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.streamContextApi, + this.messagesApi = new MessagesApi(agentClient); + this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi, this.authSession, new RetryWithRecoveryBuilder<>()); when(authSession.getSessionToken()).thenReturn("1234"); @@ -109,7 +109,7 @@ void setUp() { @Test void nonOboEndpointShouldThrowExceptionInOboMode() { - this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.streamContextApi, + this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi, new RetryWithRecoveryBuilder<>()); assertThrows(IllegalStateException.class, () -> this.service.getStream("")); @@ -120,7 +120,7 @@ void getStreamInOboMode() throws IOException { this.mockApiClient.onGet(V2_STREAM_INFO.replace("{sid}", "p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), JsonHelper.readFromClasspath("/stream/v2_stream_attributes.json")); - this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.streamContextApi, + this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi, new RetryWithRecoveryBuilder<>()); V2StreamAttributes stream = this.service.obo(this.authSession).getStream("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"); @@ -523,7 +523,7 @@ void getStreamContextTest() { void getStreamContextInOboMode() { this.mockApiClient.onGet(V1_STREAM_CONTEXT.replace("{sid}", "p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), "\"Markdown Context\""); - this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.streamContextApi, + this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi, new RetryWithRecoveryBuilder<>()); String context = this.service.obo(this.authSession).getStreamContext("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"); diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java index b498c474a..06785ac59 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java @@ -1,6 +1,5 @@ package com.symphony.bdk.spring.config; -import com.symphony.bdk.gen.api.StreamContextApi; import com.symphony.bdk.http.api.ApiClient; import com.symphony.bdk.gen.api.AppEntitlementApi; import com.symphony.bdk.gen.api.ApplicationApi; @@ -195,12 +194,6 @@ public StreamsApi streamsApi(@Qualifier("podApiClient") ApiClient podApiClient) return new StreamsApi(podApiClient); } - @Bean - @ConditionalOnMissingBean - public StreamContextApi streamsContextApi(@Qualifier("agentApiClient") ApiClient agentApiClient) { - return new StreamContextApi(agentApiClient); - } - @Bean @ConditionalOnMissingBean public SystemApi systemApi(@Qualifier("agentApiClient") ApiClient agentApiClient) { diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java index 30ed49ad2..551eb501c 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java @@ -82,6 +82,13 @@ public ApiClient podApiClient(ApiClientFactory apiClientFactory, Optional botSession, BdkConfig config) { + ApiClient client = apiClientFactory.getUsersClient(); + addCommonJwtConfig(client, botSession, config); + return client; + } + @Bean(name = "relayApiClient") public ApiClient relayApiClient(ApiClientFactory apiClientFactory) { return apiClientFactory.getRelayClient(); diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java index 6abcf7c5e..fb364856d 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java @@ -12,6 +12,7 @@ import com.symphony.bdk.core.service.session.SessionService; import com.symphony.bdk.core.service.signal.SignalService; import com.symphony.bdk.core.service.stream.StreamService; +import com.symphony.bdk.core.service.clientdata.ClientDataService; import com.symphony.bdk.core.service.user.UserService; import com.symphony.bdk.gen.api.AttachmentsApi; import com.symphony.bdk.gen.api.AuditTrailApi; @@ -26,14 +27,15 @@ import com.symphony.bdk.gen.api.SessionApi; import com.symphony.bdk.gen.api.ShareApi; import com.symphony.bdk.gen.api.SignalsApi; -import com.symphony.bdk.gen.api.StreamContextApi; import com.symphony.bdk.gen.api.StreamsApi; import com.symphony.bdk.gen.api.UserApi; import com.symphony.bdk.gen.api.UsersApi; +import com.symphony.bdk.http.api.ApiClient; import com.symphony.bdk.template.api.TemplateEngine; import org.apiguardian.api.API; import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -74,8 +76,8 @@ public SessionService oboSessionService(SessionApi sessionApi, BdkConfig config) @Bean @ConditionalOnMissingBean public StreamService oboStreamService(StreamsApi streamsApi, RoomMembershipApi roomMembershipApi, ShareApi shareApi, - StreamContextApi streamContextApi, BdkConfig config) { - return new StreamService(streamsApi, roomMembershipApi, shareApi, streamContextApi, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); + MessagesApi messagesApi, BdkConfig config) { + return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); } @Bean @@ -84,6 +86,13 @@ public UserService oboUserService(UserApi userApi, UsersApi usersApi, AuditTrail return new UserService(userApi, usersApi, auditTrailApi, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); } + @Bean + @ConditionalOnMissingBean + public ClientDataService oboClientDataService( + @Qualifier("usersApiClient") ApiClient usersApiClient, BdkConfig config) { + return new ClientDataService(usersApiClient, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); + } + @Bean @ConditionalOnMissingBean public PresenceService oboPresenceService(PresenceApi presenceApi, BdkConfig config) { diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java index 5228a722e..415122450 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java @@ -13,6 +13,7 @@ import com.symphony.bdk.core.service.session.SessionService; import com.symphony.bdk.core.service.signal.SignalService; import com.symphony.bdk.core.service.stream.StreamService; +import com.symphony.bdk.core.service.clientdata.ClientDataService; import com.symphony.bdk.core.service.user.UserService; import com.symphony.bdk.gen.api.AppEntitlementApi; import com.symphony.bdk.gen.api.ApplicationApi; @@ -30,15 +31,16 @@ import com.symphony.bdk.gen.api.SessionApi; import com.symphony.bdk.gen.api.ShareApi; import com.symphony.bdk.gen.api.SignalsApi; -import com.symphony.bdk.gen.api.StreamContextApi; import com.symphony.bdk.gen.api.StreamsApi; import com.symphony.bdk.gen.api.SystemApi; import com.symphony.bdk.gen.api.UserApi; import com.symphony.bdk.gen.api.UsersApi; +import com.symphony.bdk.http.api.ApiClient; import com.symphony.bdk.spring.service.BotInfoService; import com.symphony.bdk.template.api.TemplateEngine; import org.apiguardian.api.API; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -59,9 +61,9 @@ public SessionService sessionService(SessionApi sessionApi, AuthSession botSessi @Bean @ConditionalOnMissingBean - public StreamService streamService(StreamsApi streamsApi, RoomMembershipApi roomMembershipApi, ShareApi shareApi, StreamContextApi streamContextApi, + public StreamService streamService(StreamsApi streamsApi, RoomMembershipApi roomMembershipApi, ShareApi shareApi, MessagesApi messagesApi, AuthSession botSession, BdkConfig config) { - return new StreamService(streamsApi, roomMembershipApi, shareApi, streamContextApi, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); + return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); } @Bean @@ -70,6 +72,13 @@ public UserService userService(UserApi userApi, UsersApi usersApi, AuditTrailApi return new UserService(userApi, usersApi, auditTrailApi, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); } + @Bean + @ConditionalOnMissingBean + public ClientDataService clientDataService( + @Qualifier("usersApiClient") ApiClient usersApiClient, AuthSession botSession, BdkConfig config) { + return new ClientDataService(usersApiClient, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry())); + } + @Bean @ConditionalOnMissingBean public DisclaimerService disclaimerService(DisclaimerApi disclaimerApi, AuthSession botSession, BdkConfig config) { diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkMockedConfiguration.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkMockedConfiguration.java index 731579cb1..8f4ea362c 100644 --- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkMockedConfiguration.java +++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkMockedConfiguration.java @@ -82,6 +82,11 @@ public ApiClient getPodClient() { return this.podApiClient.getApiClient("/pod"); } + @Override + public ApiClient getUsersClient() { + return this.podApiClient.getApiClient(""); + } + @Override public ApiClient getRelayClient() {