diff --git a/.gitignore b/.gitignore index fa116c91..f94a8b2a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,13 @@ docs/contributing.md # We use YARN website/package-lock.json + +# Maven target +target/ + +# Eclipse Ignore +.classpath +.project +.settings/* +*/.settings +fdc3-java-api.code-workspace diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..d7f75dd3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}", + "vmArgs": [ + "-Dfdc3.log.level=DEBUG", + "-DFDC3_WEBSOCKET_URL=http://localhost:8090/remote/user-fce30a8d-95eb-4fb4-b34d-27ce9fdb10df/87664652dcaedaa4" + ] + }, + { + "type": "java", + "name": "Attach to Maven Debug (port 5005)", + "request": "attach", + "hostName": "localhost", + "port": 5005 + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6fa0537a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "[java]": { + "editor.formatOnSave": false, + "editor.formatOnPaste": false, + "editor.formatOnType": false + }, + "java.format.enabled": false +} \ No newline at end of file diff --git a/README.md b/README.md index 425e8694..3df5da25 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,156 @@ -![badge-labs](https://user-images.githubusercontent.com/327285/230928932-7c75f8ed-e57b-41db-9fb7-a292a13a1e58.svg) +[![FINOS - Incubating](https://cdn.jsdelivr.net/gh/finos/contrib-toolbox@master/images/badge-incubating.svg)](https://community.finos.org/docs/governance/Software-Projects/stages/incubating) # FDC3 Java API -Standardized Java API to enable integration of FDC3 for Java Desktop Applications. +A Java implementation of the [FDC3 Standard](https://fdc3.finos.org/) enabling Java desktop applications to interoperate with other FDC3-enabled applications via the [Desktop Agent Communication Protocol (DACP)](https://fdc3.finos.org/docs/api/specs/desktopAgentCommunicationProtocol). -## Installation & Development Setup +```mermaid +sequenceDiagram + participant Sail as FDC3 Sail + participant App as Java App -Prerequisite: Java 11 + Note over Sail: Add connectionUrl to App Directory for native app + Note over Sail: Display WebSocket URL in UI + Sail-->>App: connectionUrl (advertised in UI for user to copy) + Note over App: Obtain webSocketUrl, build GetAgentParams -#### FDC3 Java API + App->>Sail: WebSocket connect + Sail->>App: Connection accepted + App->>Sail: WCP4ValidateAppIdentity (identityURL: native) + Sail->>App: WCP5ValidateAppIdentityResponse (appId, instanceId, instanceUuid) + Note over App: Store appId, instanceId, instanceUuid for reconnection -In a new terminal, navigate to the `fdc3api` directory - -Build Project - -```sh -mvn clean compile + loop DACP message exchange + App->>Sail: broadcastRequest, addContextListenerRequest, etc. + Sail->>App: broadcastEvent, intentEvent, heartbeatEvent, etc. + end ``` -#### FDC3 Container +**App Identity flow:** Sail adds a `connectionUrl` (WebSocket URL) to each native app's entry in the App Directory and displays it in the Sail UI so the user can copy it (or provide it via config/launch params). The Java app uses this URL in `GetAgentParams`. For a new connection, the app sends `WCP4ValidateAppIdentity` with `identityURL: "native"`. Sail matches this to the App Directory, assigns an `appId`, and returns `instanceId` and `instanceUuid` in `WCP5ValidateAppIdentityResponse`. The app stores these (via `getInfo()`) for reconnection. After handshake, all FDC3 API calls flow as DACP messages over the WebSocket. -In a new terminal, navigate to the `fdc3container` directory +## Overview -Install Dependencies +This project provides: -```sh -npm i -``` +- **FDC3 Standard API interfaces** — Java equivalents of the FDC3 TypeScript API +- **Desktop Agent Proxy** — Client-side implementation that communicates with a Desktop Agent over WebSocket +- **GetAgent factory** — Simple entry point for connecting to a Desktop Agent +- **Cucumber testing framework** — Shared step definitions for conformance testing against the official FDC3 feature files -Run Application +## Modules -```sh -npm start -``` +| Module | Description | +| ------------------ | ------------------------------------------------------------------------------- | +| `fdc3-standard` | Core FDC3 API interfaces (`DesktopAgent`, `Channel`, `Context`, `Intent`, etc.) | +| `fdc3-schema` | Generated schema types and JSON conversion utilities | +| `fdc3-context` | Context type conversion utilities | +| `fdc3-agent-proxy` | `DesktopAgentProxy` implementation using DACP messaging | +| `fdc3-get-agent` | `GetAgent` factory for obtaining a `DesktopAgent` connection via WebSocket | -Runs on +## Requirements -#### Client +- Java 11 or later +- Maven 3.6+ +- A running FDC3 Desktop Agent that supports the [Desktop Agent Communication Protocol](https://fdc3.finos.org/docs/api/specs/desktopAgentCommunicationProtocol) (e.g., [FDC3 Sail](https://github.com/finos/FDC3-Sail)) -In a new terminal, navigate to the `client` directory +## Installation -Build Project: +### Building from Source ```sh -mvn clean compile package +mvn clean install ``` -Run the executable from Target directory +This will download the schemas for the DACP / WCP / Contexts from NPM as it runs. However, you can also run like this: ```sh -java -jar \client\target\client-1.0.0-SNAPSHOT.jar - ``` +mvn clean install -Plocal +``` -#### Stock Search React Receiver Application +which will use local schemas inside the `src/main/schemas-temp` directories. This is a temporary feature for unreleased versions of FDC3. -In a new terminal, navigate to the `fdcreceiver-react-trade-app` directory +### Maven Dependency -Install Dependencies +Once published, add to your `pom.xml`: -```sh -npm i +```xml + + org.finos.fdc3 + fdc3-get-agent + 1.0.0-SNAPSHOT + ``` -Run Application +## Usage -```sh -npm start -``` +### Connecting to a Desktop Agent + +````java +import org.finos.fdc3.getagent.GetAgent; +import org.finos.fdc3.getagent.GetAgentParams; +import org.finos.fdc3.api.DesktopAgent; +import java.util.UUID; -Runs on +// Connect to a Desktop Agent via WebSocket +GetAgentParams params = GetAgentParams.builder() + .webSocketUrl("ws://localhost:4475") // Desktop Agent WebSocket URL (required) + .instanceId(desktopAgentProvidedInstanceId) // Unique instance ID (required) + .instanceUuid(desktopAgentProvidedInstanceUuid)// Shared secret UUID (required) + .channelSelector(myChannelSelector) // Optional: custom ChannelSelector + .intentResolver(myIntentResolver) // Optional: custom IntentResolver + .build(); -## Usage with the Current State of this Repo +DesktopAgent agent = GetAgent.getAgent(params).toCompletableFuture().get(); -With the goal of our use case being to enable integration of FDC3 for Java Desktop Applications, we developed the Java API in addition to several simple applications that demonstrate its functionality and potential. The components we developed are as follows: +### Configuration via System Properties -* Java API - Our implementation of the Java API. Located in `fdc3api` -* Java Swing Sender - Client application to place orders. Located in `client` -* Adapter - the OpenFin Adapter. Located in `openfin-fdc3-adapter` -* Trade App - React based application enabled for receiving trade context from the sender. Located in `fdcreceiver-react-trade-app` -* FDC3 Container - OpenFin based container hosting the receiver environment. Located in `fdc3container` +The following system properties can be used to provide default values for `GetAgentParams`. +Values set via the builder will override these defaults. -As you interact with these applications, you will see our API in action. For example, say the user were to send the instruments from the Java Swing blotter, this action would then be reflected across the receiving web applications. In addition, we implemented a feature to allow the user to select what channel they are listening on to demonstrate the potential of our API. +| System Property | Description | +| --------------------- | ------------------------------------------------ | +| `FDC3_WEBSOCKET_URL` | Default WebSocket URL for the Desktop Agent | +| `FDC3_INSTANCE_ID` | Instance ID for the application instance (if reconnecting)| +| `FDC3_INSTANCE_UUID` | Instance ID UUID (shared secret) (if reconnecting) | -![Demo Screenshot](readme-images/demo_screenshot.png) +This allows for simplified configuration when these values are provided externally: -## Usage in a Business Environment +```java +// If system properties are set, the builder can be used with minimal configuration +// e.g., java -DFDC3_WEBSOCKET_URL=ws://localhost:4475 -DFDC3_INSTANCE_ID=my-app ... +GetAgentParams params = GetAgentParams.builder() + .channelSelector(myChannelSelector) // Only set optional overrides + .build(); +``` + +### Broadcasting Context -Some firms have existing Java desktop applications, and they want to use FDC3 to integrate with other apps that use JavaScript or other technologies. +```java +// Create and broadcast a context +Context contact = new Contact("jane@example.com", "Jane Smith"); +agent.broadcast(contact); +```` -For example, a buy-side trader using an internal Java order management system selects an order on their blotter and wants to view related analytics in an external JavaScript app provided by a broker. +### Listening for Context -This API will provide a standardized API for Java app developers to use, making it easier to switch the underlying technology that provides the FDC3 communication if required. By leveraging our FDC3 Java API in existing apps, developers will be able to create a user friendly workflow that favors shared context between applications as opposed to manual repetition by the user. +```java +// Add a context listener +agent.addContextListener("fdc3.contact", context -> { + System.out.println("Received contact: " + context); +}); +``` -## Roadmap +### Raising Intents -1. Robust testing -2. Acceptance as a FINOS standard -3. The FDC3 Java API is leveraged in a production environment -4. Review with OpenFin the FDC3 features not currently supported by their Java API. Discuss if they would be willing to implement this FDC3 Java API directly instead of using an adapter. -5. If possible, write a fully open-source implementation of the API without any dependency on a specific desktop agent vendor. This may be possible using websocket with the new desktop agent bridging spec. +```java +// Raise an intent (null app lets the resolver choose) +IntentResolution resolution = agent.raiseIntent("ViewChart", instrument, null) + .toCompletableFuture().get(); +``` ## Contributing -1. Fork it () +1. Fork the repository () 2. Create your feature branch (`git checkout -b feature/fooBar`) 3. Read our [contribution guidelines](.github/CONTRIBUTING.md) and [Community Code of Conduct](https://www.finos.org/code-of-conduct) 4. Commit your changes (`git commit -am 'Add some fooBar'`) @@ -111,11 +159,11 @@ This API will provide a standardized API for Java app developers to use, making _NOTE:_ Commits and pull requests to FINOS repositories will only be accepted from those contributors with an active, executed Individual Contributor License Agreement (ICLA) with FINOS OR who are covered under an existing and active Corporate Contribution License Agreement (CCLA) executed with FINOS. Commits from individuals not covered under an ICLA or CCLA will be flagged and blocked by the FINOS Clabot tool. Please note that some CCLAs require individuals/employees to be explicitly named on the CCLA. -*Need an ICLA? Unsure if you are covered under an existing CCLA? Email [help@finos.org](mailto:help@finos.org)* +_Need an ICLA? Unsure if you are covered under an existing CCLA? Email [help@finos.org](mailto:help@finos.org)_ ## License -Copyright 2023 Wellington Management Company LLP +Copyright 2026 Fintech Open Source Foundation (FINOS) Distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..184e95a8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +## TODO + +- Download Proxy feature files from npm (once they are published there) diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml new file mode 100644 index 00000000..d3018941 --- /dev/null +++ b/fdc3-agent-proxy/pom.xml @@ -0,0 +1,173 @@ + + + + 4.0.0 + + org.finos.fdc3 + fdc3-parent + 1.0.0-SNAPSHOT + + + fdc3-agent-proxy + FDC3 Desktop Agent Proxy + Desktop Agent Proxy implementation for FDC3 + + + UTF-8 + 11 + 11 + src/test/resources/temporary-features + 7.15.0 + 6.1.2 + 1.5.6 + + + + + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + + + + org.finos.fdc3 + fdc3-schema + ${project.version} + + + + io.github.robmoffat + standard-cucumber-steps + 1.2.1 + test + + + + + io.cucumber + cucumber-spring + ${cucumber.version} + test + + + org.springframework + spring-context + ${spring.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + test + + + org.junit.jupiter + junit-jupiter + 5.10.1 + test + + + org.junit.platform + junit-platform-suite + 1.10.1 + test + + + org.slf4j + slf4j-simple + 2.0.9 + test + + + + + org.slf4j + slf4j-api + 2.0.9 + + + + com.networknt + json-schema-validator + ${networknt.version} + test + + + + + + + + + src/test/resources + + + features/** + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + copy-fdc3-features + generate-test-resources + + copy-resources + + + ${project.build.directory}/test-classes/features + + + ${fdc3.features.source} + + *.feature + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + + + + cucumber.junit-platform.naming-strategy=long + cucumber.junit-platform.discovery.as-root-engine=false + + + + + + + + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java new file mode 100644 index 00000000..d64f10fd --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java @@ -0,0 +1,259 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.ui.Connectable; +import org.finos.fdc3.proxy.apps.AppSupport; +import org.finos.fdc3.proxy.channels.ChannelSupport; +import org.finos.fdc3.proxy.heartbeat.HeartbeatSupport; +import org.finos.fdc3.proxy.intents.IntentSupport; + +/** + * Desktop Agent Proxy implementation. + *

+ * This splits out the functionality of the desktop agent into + * app, channels and intents concerns. + */ +public class DesktopAgentProxy implements DesktopAgent, Connectable { + + private final HeartbeatSupport heartbeat; + private final ChannelSupport channels; + private final IntentSupport intents; + private final AppSupport apps; + private final List connectables; + + public DesktopAgentProxy( + HeartbeatSupport heartbeat, + ChannelSupport channels, + IntentSupport intents, + AppSupport apps, + List connectables) { + this.heartbeat = heartbeat; + this.intents = intents; + this.channels = channels; + this.apps = apps; + this.connectables = connectables; + } + + @Override + public CompletionStage addEventListener(String type, EventHandler handler) { + return channels.addEventListener(handler, type); + } + + @Override + public CompletionStage getInfo() { + return apps.getImplementationMetadata(); + } + + @Override + public CompletionStage broadcast(Context context) { + return broadcast(context, null); + } + + @Override + public CompletionStage broadcast(Context context, AppProvidableContextMetadata metadata) { + return channels.getUserChannel() + .thenCompose(channel -> { + if (channel != null) { + return channel.broadcast(context, metadata); + } else { + return CompletableFuture.completedFuture(null); + } + }); + } + + @Override + public CompletionStage addContextListener(String contextType, ContextHandler handler) { + return channels.addContextListener(handler, contextType); + } + + @Override + public CompletionStage addContextListener(ContextHandler handler) { + return channels.addContextListener(handler, null); + } + + @Override + public CompletionStage> getUserChannels() { + return channels.getUserChannels(); + } + + @Deprecated + public CompletionStage> getSystemChannels() { + return channels.getUserChannels(); + } + + @Override + public CompletionStage getOrCreateChannel(String channelId) { + return channels.getOrCreate(channelId); + } + + @Override + public CompletionStage createPrivateChannel() { + return channels.createPrivateChannel(); + } + + @Override + public CompletionStage leaveCurrentChannel() { + return channels.leaveUserChannel(); + } + + @Override + public CompletionStage joinUserChannel(String channelId) { + return channels.joinUserChannel(channelId); + } + + public CompletionStage joinChannel(String channelId) { + return channels.joinUserChannel(channelId); + } + + @Override + public CompletionStage> getCurrentChannel() { + return channels.getUserChannel() + .thenApply(Optional::ofNullable); + } + + @Override + public CompletionStage findIntent(String intent, Context context, String resultType) { + return intents.findIntent(intent, context, resultType); + } + + @Override + public CompletionStage> findIntentsByContext(Context context, String resultType) { + return intents.findIntentsByContext(context); + } + + @Override + public CompletionStage raiseIntent(String intent, Context context, AppIdentifier app) { + return intents.raiseIntent(intent, context, app); + } + + @Override + public CompletionStage raiseIntent( + String intent, Context context, AppIdentifier app, AppProvidableContextMetadata metadata) { + return intents.raiseIntent(intent, context, app, metadata); + } + + @Override + public CompletionStage raiseIntent( + String intent, Context context, AppProvidableContextMetadata metadata) { + return intents.raiseIntent(intent, context, null, metadata); + } + + @Override + public CompletionStage addIntentListener(String intent, IntentHandler handler) { + return intents.addIntentListener(intent, handler); + } + + @Override + public CompletionStage raiseIntentForContext(Context context, AppIdentifier app) { + return intents.raiseIntentForContext(context, app); + } + + @Override + public CompletionStage raiseIntentForContext( + Context context, AppIdentifier app, AppProvidableContextMetadata metadata) { + return intents.raiseIntentForContext(context, app, metadata); + } + + @Override + public CompletionStage raiseIntentForContext( + Context context, AppProvidableContextMetadata metadata) { + return intents.raiseIntentForContext(context, null, metadata); + } + + @Override + public CompletionStage open(AppIdentifier app, Context context) { + return apps.open(app, context); + } + + @Override + public CompletionStage open(AppIdentifier app, Context context, AppProvidableContextMetadata metadata) { + return apps.open(app, context, metadata); + } + + @Override + @Deprecated + public CompletionStage open(String name, Context context) { + return apps.open(name, context); + } + + @Override + public CompletionStage> findInstances(AppIdentifier app) { + return apps.findInstances(app); + } + + @Override + public CompletionStage getAppMetadata(AppIdentifier app) { + return apps.getAppMetadata(app); + } + + @Override + public CompletionStage disconnect() { + List> futures = connectables.stream() + .map(c -> c.disconnect().toCompletableFuture()) + .collect(Collectors.toList()); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + @Override + public CompletionStage connect() { + List> futures = connectables.stream() + .map(c -> c.connect().toCompletableFuture()) + .collect(Collectors.toList()); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + // Getters for internal components (useful for testing) + + public HeartbeatSupport getHeartbeat() { + return heartbeat; + } + + public ChannelSupport getChannels() { + return channels; + } + + public IntentSupport getIntents() { + return intents; + } + + public AppSupport getApps() { + return apps; + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java new file mode 100644 index 00000000..37785c62 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java @@ -0,0 +1,122 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.Predicate; + +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.schema.AddContextListenerRequestMeta; +import org.finos.fdc3.schema.SchemaConverter; + +/** + * Interface for messaging between the app and the Desktop Agent. + */ +public interface Messaging { + + /** + * Creates UUIDs used in outgoing messages. + * + * @return a new UUID string + */ + String createUUID(); + + /** + * Post an outgoing message. + * + * @param message the message to send + * @return a CompletionStage that completes when the message is sent + */ + CompletionStage post(Map message); + + /** + * Registers a listener for incoming messages. + * + * @param listener the listener to register + */ + void register(RegisterableListener listener); + + /** + * Unregister a listener with the given id. + * + * @param id the listener id + */ + void unregister(String id); + + /** + * Create a metadata element to attach to outgoing messages. + * + * @return the metadata object + */ + AddContextListenerRequestMeta createMeta(); + + /** + * Waits for a specific matching message. + * + * @param the expected response type + * @param filter predicate to match the expected message + * @param timeoutMs timeout in milliseconds + * @param timeoutErrorMessage error message if timeout occurs + * @return a CompletionStage containing the matched message + */ + CompletionStage waitFor(Predicate filter, long timeoutMs, String timeoutErrorMessage); + + /** + * Sends a request message and waits for a response. + * If the response contains a payload.error, it is thrown. + * + * @param the expected response type + * @param message the request message to send + * @param expectedTypeName the expected response type name + * @param timeoutMs timeout in milliseconds + * @return a CompletionStage containing the response + */ + CompletionStage exchange(Map message, String expectedTypeName, long timeoutMs); + + /** + * App identification used to provide source information used in + * message meta elements, IntentResolution etc. + * + * @return the app identifier + */ + AppIdentifier getAppIdentifier(); + + /** + * Disconnects the underlying message transport. + * + * @return a CompletionStage that completes when disconnected + */ + CompletionStage disconnect(); + + /** + * Get the schema converter for converting between JSON and FDC3 message types. + * + * @return the SchemaConverter instance + */ + SchemaConverter getConverter(); + + /** + * Gets the instance UUID (shared secret) for this connection. + * This is used for reconnection to prove the app's identity. + * + * @return the instance UUID + */ + String getInstanceUuid(); +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java new file mode 100644 index 00000000..5ccecb08 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java @@ -0,0 +1,78 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.apps; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.types.AppIdentifier; + +/** + * Interface for application-related operations. + */ +public interface AppSupport { + + /** + * Find instances of an application. + * + * @param app the application identifier + * @return a CompletionStage containing the list of app instances + */ + CompletionStage> findInstances(AppIdentifier app); + + /** + * Get metadata for an application. + * + * @param app the application identifier + * @return a CompletionStage containing the app metadata + */ + CompletionStage getAppMetadata(AppIdentifier app); + + /** + * Open an application. + * + * @param app the application identifier + * @param context optional context to pass + * @return a CompletionStage containing the opened app identifier + */ + CompletionStage open(AppIdentifier app, Context context); + + CompletionStage open(AppIdentifier app, Context context, AppProvidableContextMetadata metadata); + + /** + * Open an application by name. + * + * @param name the application name + * @param context optional context to pass + * @return a CompletionStage containing the opened app identifier + * @deprecated Use {@link #open(AppIdentifier, Context)} instead + */ + @Deprecated + CompletionStage open(String name, Context context); + + /** + * Get implementation metadata for the Desktop Agent. + * + * @return a CompletionStage containing the implementation metadata + */ + CompletionStage getImplementationMetadata(); +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java new file mode 100644 index 00000000..b9362790 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java @@ -0,0 +1,211 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.apps; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.errors.OpenError; +import org.finos.fdc3.api.errors.ResolveError; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.util.ContextMetadataMapper; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.*; + +/** + * Default implementation of AppSupport. + */ +public class DefaultAppSupport implements AppSupport { + + private final Messaging messaging; + private final long messageExchangeTimeout; + private final long appLaunchTimeout; + + public DefaultAppSupport(Messaging messaging, long messageExchangeTimeout, long appLaunchTimeout) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.appLaunchTimeout = appLaunchTimeout; + } + + @Override + public CompletionStage> findInstances(AppIdentifier app) { + // Build typed request + FindInstancesRequest request = new FindInstancesRequest(); + request.setType(FindInstancesRequestType.FIND_INSTANCES_REQUEST); + request.setMeta(messaging.createMeta()); + + FindInstancesRequestPayload payload = new FindInstancesRequestPayload(); + payload.setApp(app); + request.setPayload(payload); + + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "findInstancesResponse", messageExchangeTimeout) + .thenApply(response -> { + FindInstancesResponse typedResponse = messaging.getConverter() + .convertValue(response, FindInstancesResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIdentifiers() == null) { + return new ArrayList<>(); + } + + // AppMetadata extends AppIdentifier, so we can return directly + return Arrays.stream(typedResponse.getPayload().getAppIdentifiers()) + .map(am -> (AppIdentifier) am) + .collect(Collectors.toList()); + }); + } + + @Override + public CompletionStage getAppMetadata(AppIdentifier app) { + // Build typed request + GetAppMetadataRequest request = new GetAppMetadataRequest(); + request.setType(GetAppMetadataRequestType.GET_APP_METADATA_REQUEST); + request.setMeta(messaging.createMeta()); + + GetAppMetadataRequestPayload payload = new GetAppMetadataRequestPayload(); + payload.setApp(app); + request.setPayload(payload); + + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getAppMetadataResponse", messageExchangeTimeout) + .thenApply(response -> { + GetAppMetadataResponse typedResponse = messaging.getConverter() + .convertValue(response, GetAppMetadataResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppMetadata() == null) { + throw new RuntimeException(ResolveError.TargetAppUnavailable.toString()); + } + + // Schema now uses fdc3-standard AppMetadata directly + return typedResponse.getPayload().getAppMetadata(); + }); + } + + @Override + public CompletionStage open(AppIdentifier app, Context context) { + return open(app, context, null); + } + + @Override + public CompletionStage open(AppIdentifier app, Context context, AppProvidableContextMetadata metadata) { + OpenRequest request = new OpenRequest(); + request.setType(OpenRequestType.OPEN_REQUEST); + request.setMeta(messaging.createMeta()); + + OpenRequestPayload payload = new OpenRequestPayload(); + payload.setApp(app); + if (context != null) { + payload.setContext(context); + } + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + @SuppressWarnings("unchecked") + Map payloadMap = (Map) requestMap.get("payload"); + if (payloadMap != null) { + payloadMap.put("metadata", ContextMetadataMapper.toWire(metadata)); + } + + return messaging.>exchange(requestMap, "openResponse", appLaunchTimeout) + .thenApply(response -> { + OpenResponse typedResponse = messaging.getConverter() + .convertValue(response, OpenResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIdentifier() == null) { + throw new RuntimeException(OpenError.AppNotFound.toString()); + } + + return typedResponse.getPayload().getAppIdentifier(); + }); + } + + @Override + @Deprecated + public CompletionStage open(String name, Context context) { + // Create an AppIdentifier from the name string + return open(new AppIdentifier(name), context); + } + + @Override + public CompletionStage getImplementationMetadata() { + // Build typed request + GetInfoRequest request = new GetInfoRequest(); + request.setType(GetInfoRequestType.GET_INFO_REQUEST); + request.setMeta(messaging.createMeta()); + request.setPayload(new GetInfoRequestPayload()); + + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getInfoResponse", messageExchangeTimeout) + .thenApply(response -> { + GetInfoResponse typedResponse = messaging.getConverter() + .convertValue(response, GetInfoResponse.class); + + if (typedResponse.getPayload() != null && + typedResponse.getPayload().getImplementationMetadata() != null) { + // Schema now uses fdc3-standard ImplementationMetadata directly + ImplementationMetadata metadata = typedResponse.getPayload().getImplementationMetadata(); + + // Populate instanceUuid from messaging layer (local extension for reconnection) + if (metadata.getAppMetadata() != null && messaging.getInstanceUuid() != null) { + metadata.getAppMetadata().setInstanceUuid(messaging.getInstanceUuid()); + } + + return metadata; + } else { + Logger.error("Invalid response from Desktop Agent to getInfo!"); + return createUnknownImplementationMetadata(); + } + }); + } + + // ============ Conversion helpers ============ + // Schema now uses fdc3-standard AppIdentifier directly, no conversion needed + + private ImplementationMetadata createUnknownImplementationMetadata() { + ImplementationMetadata result = new ImplementationMetadata(); + result.setFdc3Version("unknown"); + result.setProvider("unknown"); + + AppMetadata appMetadata = new AppMetadata(); + appMetadata.setAppId("unknown"); + appMetadata.setInstanceId("unknown"); + result.setAppMetadata(appMetadata); + + ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); + result.setOptionalFeatures(optFeatures); + + return result; + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java new file mode 100644 index 00000000..d1ec2890 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java @@ -0,0 +1,95 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.Listener; + +/** + * Interface for channel-related operations. + */ +public interface ChannelSupport { + + /** + * Get the current user channel. + * + * @return a CompletionStage containing the current channel, or null if not joined + */ + CompletionStage getUserChannel(); + + /** + * Get all available user channels. + * + * @return a CompletionStage containing the list of user channels + */ + CompletionStage> getUserChannels(); + + /** + * Get or create an app channel with the specified ID. + * + * @param id the channel ID + * @return a CompletionStage containing the channel + */ + CompletionStage getOrCreate(String id); + + /** + * Create a new private channel. + * + * @return a CompletionStage containing the new private channel + */ + CompletionStage createPrivateChannel(); + + /** + * Leave the current user channel. + * + * @return a CompletionStage that completes when left + */ + CompletionStage leaveUserChannel(); + + /** + * Join a user channel by ID. + * + * @param id the channel ID to join + * @return a CompletionStage that completes when joined + */ + CompletionStage joinUserChannel(String id); + + /** + * Add a context listener. + * + * @param handler the context handler + * @param type the context type to listen for, or null for all types + * @return a CompletionStage containing the listener + */ + CompletionStage addContextListener(ContextHandler handler, String type); + + /** + * Add an event listener. + * + * @param handler the event handler + * @param type the event type to listen for, or null for all types + * @return a CompletionStage containing the listener + */ + CompletionStage addEventListener(EventHandler handler, String type); +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java new file mode 100644 index 00000000..07d37c08 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java @@ -0,0 +1,232 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.DisplayMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.ContextWithMetadata; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.ContextMetadataMapper; +import org.finos.fdc3.proxy.listeners.DefaultContextListener; +import org.finos.fdc3.schema.*; + +/** + * Default implementation of a Channel. + */ +@JsonAutoDetect( + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + fieldVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE) +public class DefaultChannel implements Channel { + + @JsonIgnore + protected final Messaging messaging; + @JsonIgnore + protected final long messageExchangeTimeout; + private final String id; + private final Type type; + @JsonIgnore + private final DisplayMetadata displayMetadata; + + public DefaultChannel( + Messaging messaging, + long messageExchangeTimeout, + String id, + Type type, + DisplayMetadata displayMetadata) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.id = id; + this.type = type; + this.displayMetadata = displayMetadata; + } + + @Override + @JsonProperty("id") + public String getId() { + return id; + } + + @Override + @JsonIgnore + public Type getType() { + return type; + } + + @JsonProperty("type") + @JsonGetter("type") + public String getTypeValue() { + return type != null ? type.getValue() : null; + } + + @Override + @JsonProperty("displayMetadata") + public DisplayMetadata getDisplayMetadata() { + return displayMetadata; + } + + @Override + @JsonIgnore + public CompletionStage broadcast(Context context) { + return broadcast(context, null); + } + + @Override + @JsonIgnore + public CompletionStage broadcast(Context context, AppProvidableContextMetadata metadata) { + BroadcastRequest request = new BroadcastRequest(); + request.setType(BroadcastRequestType.BROADCAST_REQUEST); + request.setMeta(messaging.createMeta()); + + BroadcastRequestPayload payload = new BroadcastRequestPayload(); + payload.setChannelID(id); + payload.setContext(context); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + @SuppressWarnings("unchecked") + Map payloadMap = (Map) requestMap.get("payload"); + if (payloadMap != null) { + payloadMap.put("metadata", ContextMetadataMapper.toWire(metadata)); + } + + return messaging.>exchange(requestMap, "broadcastResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + @JsonIgnore + public CompletionStage> getCurrentContext() { + return getCurrentContext(null); + } + + @Override + @JsonIgnore + public CompletionStage> getCurrentContext(String contextType) { + GetCurrentContextRequest request = new GetCurrentContextRequest(); + request.setType(GetCurrentContextRequestType.GET_CURRENT_CONTEXT_REQUEST); + request.setMeta(messaging.createMeta()); + + GetCurrentContextRequestPayload payload = new GetCurrentContextRequestPayload(); + payload.setChannelID(id); + payload.setContextType(contextType); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getCurrentContextResponse", messageExchangeTimeout) + .thenApply(response -> { + Context context = extractContextFromResponse(response); + return context != null ? Optional.of(context) : Optional.empty(); + }); + } + + @Override + @JsonIgnore + public CompletionStage> getCurrentContextWithMetadata(String contextType) { + GetCurrentContextRequest request = new GetCurrentContextRequest(); + request.setType(GetCurrentContextRequestType.GET_CURRENT_CONTEXT_REQUEST); + request.setMeta(messaging.createMeta()); + + GetCurrentContextRequestPayload payload = new GetCurrentContextRequestPayload(); + payload.setChannelID(id); + payload.setContextType(contextType); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getCurrentContextResponse", messageExchangeTimeout) + .thenApply(response -> { + Context context = extractContextFromResponse(response); + if (context == null) { + return Optional.empty(); + } + + GetCurrentContextResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentContextResponse.class); + Map responsePayload = (Map) response.get("payload"); + Map payloadMetadata = responsePayload != null + ? (Map) responsePayload.get("metadata") + : null; + Object messageTimestamp = typedResponse.getMeta() != null + ? typedResponse.getMeta().getTimestamp() + : null; + ContextMetadata metadata = ContextMetadataMapper.fromWire(payloadMetadata, messageTimestamp); + return Optional.of(new ContextWithMetadata(context, metadata)); + }); + } + + @Override + @JsonIgnore + public CompletionStage addContextListener(String contextType, ContextHandler handler) { + return addContextListenerInner(contextType, handler); + } + + @Override + @JsonIgnore + @Deprecated + public CompletionStage addContextListener(ContextHandler handler) { + return addContextListener(null, handler); + } + + protected CompletionStage addContextListenerInner(String contextType, ContextHandler handler) { + DefaultContextListener listener = new DefaultContextListener( + messaging, + messageExchangeTimeout, + id, + contextType, + handler + ); + return listener.register().thenApply(v -> listener); + } + + @SuppressWarnings("unchecked") + private static Context extractContextFromResponse(Map response) { + Map payload = (Map) response.get("payload"); + if (payload == null) { + return null; + } + Object ctx = payload.get("context"); + if (ctx == null) { + return null; + } + if (ctx instanceof Context) { + return (Context) ctx; + } + if (ctx instanceof Map) { + return Context.fromMap((Map) ctx); + } + return null; + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java new file mode 100644 index 00000000..bd375887 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java @@ -0,0 +1,372 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.errors.ChannelError; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.ui.ChannelSelector; +import org.finos.fdc3.api.ui.Connectable; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.DesktopAgentEventListener; +import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.CreatePrivateChannelRequest; +import org.finos.fdc3.schema.CreatePrivateChannelRequestPayload; +import org.finos.fdc3.schema.CreatePrivateChannelRequestType; +import org.finos.fdc3.schema.CreatePrivateChannelResponse; +import org.finos.fdc3.schema.GetCurrentChannelRequest; +import org.finos.fdc3.schema.GetCurrentChannelRequestPayload; +import org.finos.fdc3.schema.GetCurrentChannelRequestType; +import org.finos.fdc3.schema.GetCurrentChannelResponse; +import org.finos.fdc3.schema.GetOrCreateChannelRequest; +import org.finos.fdc3.schema.GetOrCreateChannelRequestPayload; +import org.finos.fdc3.schema.GetOrCreateChannelRequestType; +import org.finos.fdc3.schema.GetOrCreateChannelResponse; +import org.finos.fdc3.schema.GetUserChannelsRequest; +import org.finos.fdc3.schema.GetUserChannelsRequestPayload; +import org.finos.fdc3.schema.GetUserChannelsRequestType; +import org.finos.fdc3.schema.GetUserChannelsResponse; +import org.finos.fdc3.schema.JoinUserChannelRequest; +import org.finos.fdc3.schema.JoinUserChannelRequestPayload; +import org.finos.fdc3.schema.JoinUserChannelRequestType; +import org.finos.fdc3.schema.LeaveCurrentChannelRequest; +import org.finos.fdc3.schema.LeaveCurrentChannelRequestPayload; +import org.finos.fdc3.schema.LeaveCurrentChannelRequestType; + +/** + * Default implementation of ChannelSupport. + */ +public class DefaultChannelSupport implements ChannelSupport, Connectable { + + private final Messaging messaging; + private final ChannelSelector channelSelector; + private final long messageExchangeTimeout; + private List userChannels = null; + private Channel currentChannel = null; + private final List userChannelListeners = new ArrayList<>(); + private boolean userChannelChangedListenerRegistered = false; + + public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelector, long messageExchangeTimeout) { + this.messaging = messaging; + this.channelSelector = channelSelector; + this.messageExchangeTimeout = messageExchangeTimeout; + + // Set up channel change callback + channelSelector.setChannelChangeCallback(channelId -> { + Logger.debug("Channel selector reports channel changed: {}", channelId); + if (channelId == null) { + leaveUserChannel(); + } else { + joinUserChannel(channelId); + } + }); + } + + @Override + public CompletionStage connect() { + CompletionStage loadChannel = getUserChannel().thenApply(channel -> { + currentChannel = channel; + return null; + }); + if (userChannelChangedListenerRegistered) { + return loadChannel; + } + userChannelChangedListenerRegistered = true; + return loadChannel.thenCompose(v -> registerUserChannelChangedListener()); + } + + /** + * Update cached user channel synchronously from a channel id (e.g. from userChannelChanged). + * Must run before any await in event handlers so other listeners calling getCurrentChannel() + * do not read a stale channel. + */ + private void applyCurrentChannelFromId(String channelId) { + if (channelId == null) { + currentChannel = null; + return; + } + if (userChannels != null) { + Channel cached = userChannels.stream() + .filter(c -> channelId.equals(c.getId())) + .findFirst() + .orElse(null); + if (cached != null) { + currentChannel = cached; + } else if (currentChannel == null || !channelId.equals(currentChannel.getId())) { + currentChannel = null; + } + } else if (currentChannel == null || !channelId.equals(currentChannel.getId())) { + currentChannel = null; + } + } + + private CompletionStage registerUserChannelChangedListener() { + return addEventListener(event -> { + @SuppressWarnings("unchecked") + Map details = (Map) event.getDetails(); + String newChannelId = details != null ? (String) details.get("currentChannelId") : null; + Logger.debug("Desktop Agent reports channel changed: {}", newChannelId); + + applyCurrentChannelFromId(newChannelId); + + Channel theChannel = currentChannel; + CompletionStage resolveChannel; + if (newChannelId != null && theChannel == null) { + resolveChannel = getUserChannels().thenApply(channels -> channels.stream() + .filter(c -> newChannelId.equals(c.getId())) + .findFirst() + .orElse(null)); + } else { + resolveChannel = CompletableFuture.completedFuture(theChannel); + } + + resolveChannel.thenCompose(channel -> { + if (newChannelId != null && channel == null) { + Logger.warn( + "Received user channel update with unknown user channel (user channel listeners will not work): {}", + newChannelId); + } + currentChannel = channel; + return getUserChannelsCached(); + }).thenCompose(channels -> { + channelSelector.updateChannel(currentChannel != null ? currentChannel.getId() : null, channels); + CompletionStage notify = CompletableFuture.completedFuture(null); + for (UserChannelContextListener listener : userChannelListeners) { + notify = notify.thenCompose(v -> listener.changeChannel()); + } + return notify; + }); + }, "userChannelChanged").thenApply(listener -> null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage addEventListener(EventHandler handler, String type) { + DesktopAgentEventListener listener = new DesktopAgentEventListener( + messaging, messageExchangeTimeout, type, handler); + return listener.register().thenApply(v -> listener); + } + + @Override + public CompletionStage getUserChannel() { + if (currentChannel != null) { + return CompletableFuture.completedFuture(currentChannel); + } + + GetCurrentChannelRequest request = new GetCurrentChannelRequest(); + request.setType(GetCurrentChannelRequestType.GET_CURRENT_CHANNEL_REQUEST); + request.setMeta(messaging.createMeta()); + request.setPayload(new GetCurrentChannelRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getCurrentChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + GetCurrentChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentChannelResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getChannel() == null) { + return null; + } + + org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); + // Schema now uses fdc3-standard DisplayMetadata directly + return new DefaultChannel(messaging, messageExchangeTimeout, + schemaChannel.getID(), Channel.Type.User, schemaChannel.getDisplayMetadata()); + }); + } + + private CompletionStage> getUserChannelsCached() { + if (userChannels != null) { + return CompletableFuture.completedFuture(userChannels); + } + return getUserChannels(); + } + + @Override + public CompletionStage> getUserChannels() { + GetUserChannelsRequest request = new GetUserChannelsRequest(); + request.setType(GetUserChannelsRequestType.GET_USER_CHANNELS_REQUEST); + request.setMeta(messaging.createMeta()); + request.setPayload(new GetUserChannelsRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getUserChannelsResponse", messageExchangeTimeout) + .thenApply(response -> { + GetUserChannelsResponse typedResponse = messaging.getConverter() + .convertValue(response, GetUserChannelsResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getUserChannels() == null) { + userChannels = new ArrayList<>(); + return userChannels; + } + + // Schema now uses fdc3-standard DisplayMetadata directly + userChannels = Arrays.stream(typedResponse.getPayload().getUserChannels()) + .map(c -> (Channel) new DefaultChannel( + messaging, messageExchangeTimeout, c.getID(), Channel.Type.User, + c.getDisplayMetadata())) + .collect(Collectors.toList()); + + return userChannels; + }); + } + + @Override + public CompletionStage getOrCreate(String id) { + GetOrCreateChannelRequest request = new GetOrCreateChannelRequest(); + request.setType(GetOrCreateChannelRequestType.GET_OR_CREATE_CHANNEL_REQUEST); + request.setMeta(messaging.createMeta()); + + GetOrCreateChannelRequestPayload payload = new GetOrCreateChannelRequestPayload(); + payload.setChannelID(id); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getOrCreateChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + GetOrCreateChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, GetOrCreateChannelResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getChannel() == null) { + throw new RuntimeException(ChannelError.CreationFailed.toString()); + } + + org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); + // Schema now uses fdc3-standard DisplayMetadata directly + return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, + schemaChannel.getDisplayMetadata()); + }); + } + + @Override + public CompletionStage createPrivateChannel() { + CreatePrivateChannelRequest request = new CreatePrivateChannelRequest(); + request.setType(CreatePrivateChannelRequestType.CREATE_PRIVATE_CHANNEL_REQUEST); + request.setMeta(messaging.createMeta()); + request.setPayload(new CreatePrivateChannelRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging + .>exchange(requestMap, "createPrivateChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + CreatePrivateChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, CreatePrivateChannelResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getPrivateChannel() == null) { + throw new RuntimeException(ChannelError.CreationFailed.toString()); + } + + String id = typedResponse.getPayload().getPrivateChannel().getID(); + return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); + }); + } + + @Override + public CompletionStage leaveUserChannel() { + LeaveCurrentChannelRequest request = new LeaveCurrentChannelRequest(); + request.setType(LeaveCurrentChannelRequestType.LEAVE_CURRENT_CHANNEL_REQUEST); + request.setMeta(messaging.createMeta()); + request.setPayload(new LeaveCurrentChannelRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging + .>exchange(requestMap, "leaveCurrentChannelResponse", messageExchangeTimeout) + .thenCompose(response -> { + currentChannel = null; + return getUserChannelsCached().thenAccept(channels -> { + channelSelector.updateChannel(null, channels); + }); + }); + } + + @Override + public CompletionStage joinUserChannel(String id) { + JoinUserChannelRequest request = new JoinUserChannelRequest(); + request.setType(JoinUserChannelRequestType.JOIN_USER_CHANNEL_REQUEST); + request.setMeta(messaging.createMeta()); + + JoinUserChannelRequestPayload payload = new JoinUserChannelRequestPayload(); + payload.setChannelID(id); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "joinUserChannelResponse", messageExchangeTimeout) + .thenCompose(response -> getUserChannelsCached()) + .thenCompose(channels -> { + currentChannel = channels.stream() + .filter(c -> id.equals(c.getId())) + .findFirst() + .orElse(null); + + if (currentChannel == null) { + throw new RuntimeException(ChannelError.NoChannelFound.toString()); + } + + channelSelector.updateChannel(id, channels); + + CompletionStage notify = CompletableFuture.completedFuture(null); + for (UserChannelContextListener listener : userChannelListeners) { + notify = notify.thenCompose(v -> listener.changeChannel()); + } + return notify; + }); + } + + @Override + public CompletionStage addContextListener(ContextHandler handler, String type) { + DefaultUserChannelContextListener listener = new DefaultUserChannelContextListener(this, messaging, + messageExchangeTimeout, type, handler); + userChannelListeners.add(listener); + return listener.register().thenApply(v -> listener); + } + + // Package-private for UserChannelContextListener access + Channel getCurrentChannelInternal() { + return currentChannel; + } + + // Package-private for UserChannelContextListener to remove itself on + // unsubscribe + void removeUserChannelListener(UserChannelContextListener listener) { + userChannelListeners.remove(listener); + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java new file mode 100644 index 00000000..cb41d08a --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java @@ -0,0 +1,140 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.listeners.AbstractPrivateChannelEventListener; +import org.finos.fdc3.proxy.listeners.DefaultContextListener; +import org.finos.fdc3.proxy.listeners.PrivateChannelAddContextEventListener; +import org.finos.fdc3.proxy.listeners.PrivateChannelDisconnectEventListener; +import org.finos.fdc3.proxy.listeners.PrivateChannelNullEventListener; +import org.finos.fdc3.proxy.listeners.PrivateChannelUnsubscribeEventListener; +import org.finos.fdc3.schema.*; + +/** + * Default implementation of a PrivateChannel. + */ +public class DefaultPrivateChannel extends DefaultChannel implements PrivateChannel { + + public DefaultPrivateChannel(Messaging messaging, long messageExchangeTimeout, String id) { + super(messaging, messageExchangeTimeout, id, Channel.Type.Private, null); + } + + @Override + public CompletionStage addEventListener(String type, EventHandler handler) { + AbstractPrivateChannelEventListener listener; + + if (type == null) { + listener = new PrivateChannelNullEventListener(messaging, messageExchangeTimeout, getId(), handler); + } else { + switch (type) { + case "addContextListener": + listener = new PrivateChannelAddContextEventListener(messaging, messageExchangeTimeout, getId(), handler); + break; + case "unsubscribe": + listener = new PrivateChannelUnsubscribeEventListener(messaging, messageExchangeTimeout, getId(), handler); + break; + case "disconnect": + listener = new PrivateChannelDisconnectEventListener(messaging, messageExchangeTimeout, getId(), handler); + break; + default: + throw new RuntimeException("Unsupported event type: " + type); + } + } + + return listener.register().thenApply(v -> listener); + } + + @Override + public CompletionStage onAddContextListener(EventHandler handler) { + // Adapt handler type for differences between addEventListener and onAddContextListener handler types + PrivateChannelAddContextEventListener listener = new PrivateChannelAddContextEventListener( + messaging, messageExchangeTimeout, getId(), + event -> { + @SuppressWarnings("unchecked") + Map details = (Map) event.getDetails(); + String contextType = details != null ? (String) details.get("contextType") : null; + handler.handleEvent(new FDC3Event(FDC3Event.Type.ADD_CONTEXT_LISTENER, details)); + }); + // Register asynchronously (fire and forget) like TypeScript + return listener.register().thenApply(v -> listener); + } + + @Override + public CompletionStage onUnsubscribe(EventHandler handler) { + // Adapt handler type for differences between addEventListener and onUnsubscribe handler types + PrivateChannelUnsubscribeEventListener listener = new PrivateChannelUnsubscribeEventListener( + messaging, messageExchangeTimeout, getId(), + event -> { + @SuppressWarnings("unchecked") + Map details = (Map) event.getDetails(); + String contextType = details != null ? (String) details.get("contextType") : null; + handler.handleEvent(new FDC3Event(FDC3Event.Type.ON_UNSUBSCRIBE, details)); + }); + // Register asynchronously (fire and forget) like TypeScript + return listener.register().thenApply(v -> listener); + } + + @Override + public CompletionStage onDisconnect(EventHandler handler) { + // Adapt handler type for differences between addEventListener and onDisconnect handler types + PrivateChannelDisconnectEventListener listener = new PrivateChannelDisconnectEventListener( + messaging, messageExchangeTimeout, getId(), + event -> handler.handleEvent(new FDC3Event(FDC3Event.Type.ON_DISCONNECT, null))); + return listener.register().thenApply(v -> listener); + } + + @Override + public void disconnect() { + PrivateChannelDisconnectRequest request = new PrivateChannelDisconnectRequest(); + request.setType(PrivateChannelDisconnectRequestType.PRIVATE_CHANNEL_DISCONNECT_REQUEST); + request.setMeta(messaging.createMeta()); + + PrivateChannelDisconnectRequestPayload payload = new PrivateChannelDisconnectRequestPayload(); + payload.setChannelID(getId()); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + try { + messaging.>exchange(requestMap, "privateChannelDisconnectResponse", messageExchangeTimeout) + .toCompletableFuture().get(); + } catch (Exception e) { + throw new RuntimeException("Failed to disconnect private channel", e); + } + } + + @Override + protected CompletionStage addContextListenerInner(String contextType, ContextHandler handler) { + DefaultContextListener listener = new DefaultContextListener( + messaging, + messageExchangeTimeout, + getId(), + contextType, + handler + ); + return listener.register().thenApply(v -> listener); + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java new file mode 100644 index 00000000..1441e0be --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java @@ -0,0 +1,106 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.DefaultContextListener; + +/** + * Context listener that tracks user channel changes. + * This extends DefaultContextListener and adds support for changing channels + * when the user changes their current user channel. + */ +public class DefaultUserChannelContextListener extends DefaultContextListener implements UserChannelContextListener { + + private final DefaultChannelSupport channelSupport; + + public DefaultUserChannelContextListener( + DefaultChannelSupport channelSupport, + Messaging messaging, + long messageExchangeTimeout, + String contextType, + ContextHandler handler) { + super(messaging, messageExchangeTimeout, null, contextType, handler, "broadcastEvent"); + this.channelSupport = channelSupport; + } + + @Override + public CompletionStage register() { + return super.register().thenCompose(v -> changeChannel()); + } + + /** + * Called when the user channel changes. Gets the current context from the + * current channel and notifies the handler if there is one. + */ + @Override + public CompletionStage changeChannel() { + Channel currentChannel = channelSupport.getCurrentChannelInternal(); + if (currentChannel == null) { + return CompletableFuture.completedFuture(null); + } + return currentChannel.getCurrentContextWithMetadata(contextType) + .thenAccept(resultOpt -> { + resultOpt.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); + }); + } + + @Override + public CompletionStage unsubscribe() { + channelSupport.removeUserChannelListener(this); + return super.unsubscribe(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!messageType.equals(type)) { + return false; + } + + Map payload = (Map) message.get("payload"); + if (payload == null) { + return false; + } + + // Check if on matching channel or open broadcast + String msgChannelId = (String) payload.get("channelId"); + Channel currentChannel = channelSupport.getCurrentChannelInternal(); + boolean onMatchingChannel = currentChannel != null && currentChannel.getId().equals(msgChannelId); + boolean openBroadcast = msgChannelId == null; + + if (!onMatchingChannel && !openBroadcast) { + return false; + } + + Map context = (Map) payload.get("context"); + if (context == null) { + return false; + } + + String msgContextType = (String) context.get("type"); + return contextType == null || contextType.equals(msgContextType); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java new file mode 100644 index 00000000..3a702084 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java @@ -0,0 +1,36 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.channels; + +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.listeners.RegisterableListener; + +/** + * This is a special version of a ContextListener created when the user calls the + * fdc3.addContextListener method. In this scenario, the listener will respond to broadcasts + * on whatever is the current user channel. + */ +public interface UserChannelContextListener extends RegisterableListener { + + /** + * This method is called when the user channel changes. The listener should then + * call its handler with the latest piece of relevant channel state and start responding to + * events on the new channelId. + */ + CompletionStage changeChannel(); +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java new file mode 100644 index 00000000..b31f1729 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java @@ -0,0 +1,139 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.heartbeat; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; + +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.AddContextListenerRequestMeta; +import org.finos.fdc3.schema.HeartbeatAcknowledgementRequest; +import org.finos.fdc3.schema.HeartbeatAcknowledgementRequestPayload; +import org.finos.fdc3.schema.HeartbeatAcknowledgementRequestType; + +/** + * Default implementation of HeartbeatSupport. + */ +public class DefaultHeartbeatSupport implements HeartbeatSupport { + + private final Messaging messaging; + private final long heartbeatIntervalMs; + private final ScheduledExecutorService scheduler; + private ScheduledFuture heartbeatTask; + private RegisterableListener heartbeatListener; + + public DefaultHeartbeatSupport(Messaging messaging, long heartbeatIntervalMs) { + this.messaging = messaging; + this.heartbeatIntervalMs = heartbeatIntervalMs; + this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "fdc3-heartbeat"); + t.setDaemon(true); + return t; + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage connect() { + // Register listener for heartbeat events + String id = messaging.createUUID(); + heartbeatListener = new RegisterableListener() { + @Override + public String getId() { + return id; + } + + @Override + public boolean filter(Map message) { + String type = (String) message.get("type"); + return "heartbeatEvent".equals(type); + } + + @Override + public void action(Map message) { + Map payload = (Map) message.get("payload"); + String timestamp = payload != null ? (String) payload.get("timestamp") : null; + Logger.debug("Received heartbeat at {}", timestamp); + + // Respond to heartbeat + respondToHeartbeat(message); + } + + @Override + public CompletionStage register() { + messaging.register(this); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage unsubscribe() { + messaging.unregister(id); + return CompletableFuture.completedFuture(null); + } + }; + + messaging.register(heartbeatListener); + return CompletableFuture.completedFuture(null); + } + + @SuppressWarnings("unchecked") + private void respondToHeartbeat(Map heartbeatEvent) { + try { + Map meta = (Map) heartbeatEvent.get("meta"); + String eventUuid = meta != null ? (String) meta.get("eventUuid") : null; + + HeartbeatAcknowledgementRequest request = new HeartbeatAcknowledgementRequest(); + request.setType(HeartbeatAcknowledgementRequestType.HEARTBEAT_ACKNOWLEDGEMENT_REQUEST); + + AddContextListenerRequestMeta requestMeta = messaging.createMeta(); + // Set the requestUuid to match the eventUuid for correlation + if (eventUuid != null) { + requestMeta.setRequestUUID(eventUuid); + } + request.setMeta(requestMeta); + + HeartbeatAcknowledgementRequestPayload payload = new HeartbeatAcknowledgementRequestPayload(); + payload.setHeartbeatEventUUID(eventUuid); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + messaging.post(requestMap); + } catch (Exception e) { + Logger.error("Failed to respond to heartbeat", e); + } + } + + @Override + public CompletionStage disconnect() { + if (heartbeatTask != null) { + heartbeatTask.cancel(true); + } + if (heartbeatListener != null) { + messaging.unregister(heartbeatListener.getId()); + } + + scheduler.shutdown(); + return messaging.disconnect(); + } +} diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Nothing.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java similarity index 62% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Nothing.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java index 700ee8dd..26b68c71 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/context/Nothing.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.proxy.heartbeat; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.finos.fdc3.api.ui.Connectable; -@JsonPropertyOrder({ - "type" -}) -public class Nothing extends Context { - public static String TYPE = "fdc3.nothing"; - - public Nothing() { - super(TYPE); - } +/** + * Interface for heartbeat support. + * Extends Connectable to allow connection management. + */ +public interface HeartbeatSupport extends Connectable { + // Heartbeat-specific methods can be added here if needed } + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java new file mode 100644 index 00000000..708d3830 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java @@ -0,0 +1,79 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.intents; + +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.proxy.Messaging; + +/** + * Default implementation of IntentResolution. + */ +public class DefaultIntentResolution implements IntentResolution { + + private final Messaging messaging; + private final long messageExchangeTimeout; + private final CompletionStage resultPromise; + private final CompletionStage resultMetadataPromise; + private final AppIdentifier source; + private final String intent; + + public DefaultIntentResolution( + Messaging messaging, + long messageExchangeTimeout, + CompletionStage resultPromise, + CompletionStage resultMetadataPromise, + AppIdentifier source, + String intent) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.resultPromise = resultPromise; + this.resultMetadataPromise = resultMetadataPromise; + this.source = source; + this.intent = intent; + } + + @Override + public AppIdentifier getSource() { + return source; + } + + @Override + public String getIntent() { + return intent; + } + + @Override + public Optional getVersion() { + return Optional.empty(); + } + + @Override + public CompletionStage getResult() { + return resultPromise; + } + + @Override + public CompletionStage getResultMetadata() { + return resultMetadataPromise; + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java new file mode 100644 index 00000000..dda62d47 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java @@ -0,0 +1,365 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.intents; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.errors.ResolveError; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.ui.IntentResolver; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.channels.DefaultChannel; +import org.finos.fdc3.proxy.channels.DefaultPrivateChannel; +import org.finos.fdc3.proxy.listeners.DefaultIntentListener; +import org.finos.fdc3.proxy.util.ContextMetadataMapper; +import org.finos.fdc3.schema.*; + +/** + * Default implementation of IntentSupport. + */ +public class DefaultIntentSupport implements IntentSupport { + + private final Messaging messaging; + private final IntentResolver intentResolver; + private final long messageExchangeTimeout; + private final long appLaunchTimeout; + + public DefaultIntentSupport( + Messaging messaging, + IntentResolver intentResolver, + long messageExchangeTimeout, + long appLaunchTimeout) { + this.messaging = messaging; + this.intentResolver = intentResolver; + this.messageExchangeTimeout = messageExchangeTimeout; + this.appLaunchTimeout = appLaunchTimeout; + } + + @Override + public CompletionStage findIntent(String intent, Context context, String resultType) { + FindIntentRequest request = new FindIntentRequest(); + request.setType(FindIntentRequestType.FIND_INTENT_REQUEST); + request.setMeta(messaging.createMeta()); + + FindIntentRequestPayload payload = new FindIntentRequestPayload(); + payload.setIntent(intent); + if (context != null) { + payload.setContext(context); + } + if (resultType != null) { + payload.setResultType(resultType); + } + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "findIntentResponse", messageExchangeTimeout) + .thenApply(response -> { + FindIntentResponse typedResponse = messaging.getConverter() + .convertValue(response, FindIntentResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIntent() == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + // Schema types now use fdc3-standard types directly + AppIntent appIntent = typedResponse.getPayload().getAppIntent(); + if (appIntent.getApps() == null || appIntent.getApps().length == 0) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + return appIntent; + }); + } + + @Override + public CompletionStage> findIntentsByContext(Context context) { + FindIntentsByContextRequest request = new FindIntentsByContextRequest(); + request.setType(FindIntentsByContextRequestType.FIND_INTENTS_BY_CONTEXT_REQUEST); + request.setMeta(messaging.createMeta()); + + FindIntentsByContextRequestPayload payload = new FindIntentsByContextRequestPayload(); + payload.setContext(context); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "findIntentsByContextResponse", messageExchangeTimeout) + .thenApply(response -> { + FindIntentsByContextResponse typedResponse = messaging.getConverter() + .convertValue(response, FindIntentsByContextResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIntents() == null || + typedResponse.getPayload().getAppIntents().length == 0) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + // Schema types now use fdc3-standard AppIntent directly + return Arrays.asList(typedResponse.getPayload().getAppIntents()); + }); + } + + @Override + public CompletionStage raiseIntent(String intent, Context context, AppIdentifier app) { + return raiseIntent(intent, context, app, null); + } + + @Override + public CompletionStage raiseIntent( + String intent, Context context, AppIdentifier app, AppProvidableContextMetadata metadata) { + AddContextListenerRequestMeta meta = messaging.createMeta(); + + RaiseIntentRequest request = new RaiseIntentRequest(); + request.setType(RaiseIntentRequestType.RAISE_INTENT_REQUEST); + request.setMeta(meta); + + RaiseIntentRequestPayload payload = new RaiseIntentRequestPayload(); + payload.setIntent(intent); + payload.setContext(context); + if (app != null) { + payload.setApp(app); + } + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + @SuppressWarnings("unchecked") + Map payloadMap = (Map) requestMap.get("payload"); + if (payloadMap != null) { + payloadMap.put("metadata", ContextMetadataMapper.toWireForIntentRequest(metadata, messaging::createUUID)); + } + + return messaging.>exchange(requestMap, "raiseIntentResponse", appLaunchTimeout) + .thenCompose(response -> { + RaiseIntentResponse typedResponse = messaging.getConverter() + .convertValue(response, RaiseIntentResponse.class); + + if (typedResponse.getPayload() == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + AppIntent schemaAppIntent = typedResponse.getPayload().getAppIntent(); + org.finos.fdc3.schema.IntentResolution schemaIntentResolution = + typedResponse.getPayload().getIntentResolution(); + + if (schemaAppIntent == null && schemaIntentResolution == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + if (schemaAppIntent != null) { + return intentResolver.chooseIntent(List.of(schemaAppIntent), context) + .thenCompose(choice -> { + if (choice == null) { + throw new RuntimeException(ResolveError.UserCancelled.toString()); + } + return raiseIntent(intent, context, choice.getAppId(), metadata); + }); + } + + AppIdentifier source = schemaIntentResolution.getSource(); + String resolvedIntent = schemaIntentResolution.getIntent(); + ResultPromises promises = createResultPromises(meta.getRequestUUID(), source); + return CompletableFuture.completedFuture(new DefaultIntentResolution( + messaging, + messageExchangeTimeout, + promises.result, + promises.resultMetadata, + source, + resolvedIntent)); + }); + } + + @Override + public CompletionStage raiseIntentForContext(Context context, AppIdentifier app) { + return raiseIntentForContext(context, app, null); + } + + @Override + public CompletionStage raiseIntentForContext( + Context context, AppIdentifier app, AppProvidableContextMetadata metadata) { + AddContextListenerRequestMeta meta = messaging.createMeta(); + + RaiseIntentForContextRequest request = new RaiseIntentForContextRequest(); + request.setType(RaiseIntentForContextRequestType.RAISE_INTENT_FOR_CONTEXT_REQUEST); + request.setMeta(meta); + + RaiseIntentForContextRequestPayload payload = new RaiseIntentForContextRequestPayload(); + payload.setContext(context); + if (app != null) { + payload.setApp(app); + } + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); + @SuppressWarnings("unchecked") + Map raiseForContextPayload = (Map) requestMap.get("payload"); + if (raiseForContextPayload != null) { + raiseForContextPayload.put("metadata", ContextMetadataMapper.toWireForIntentRequest(metadata, messaging::createUUID)); + } + + return messaging.>exchange(requestMap, "raiseIntentForContextResponse", appLaunchTimeout) + .thenCompose(response -> { + RaiseIntentForContextResponse typedResponse = messaging.getConverter() + .convertValue(response, RaiseIntentForContextResponse.class); + + if (typedResponse.getPayload() == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + AppIntent[] schemaAppIntents = typedResponse.getPayload().getAppIntents(); + org.finos.fdc3.schema.IntentResolution schemaIntentResolution = + typedResponse.getPayload().getIntentResolution(); + + if ((schemaAppIntents == null || schemaAppIntents.length == 0) && schemaIntentResolution == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + if (schemaAppIntents != null && schemaAppIntents.length > 0) { + return intentResolver.chooseIntent(Arrays.asList(schemaAppIntents), context) + .thenCompose(choice -> { + if (choice == null) { + throw new RuntimeException(ResolveError.UserCancelled.toString()); + } + return raiseIntent(choice.getIntent(), context, choice.getAppId(), metadata); + }); + } + + AppIdentifier source = schemaIntentResolution.getSource(); + String resolvedIntent = schemaIntentResolution.getIntent(); + ResultPromises promises = createResultPromises(meta.getRequestUUID(), source); + return CompletableFuture.completedFuture(new DefaultIntentResolution( + messaging, + messageExchangeTimeout, + promises.result, + promises.resultMetadata, + source, + resolvedIntent)); + }); + } + + @Override + public CompletionStage addIntentListener(String intent, IntentHandler handler) { + DefaultIntentListener listener = new DefaultIntentListener(messaging, intent, handler, messageExchangeTimeout); + return listener.register().thenApply(v -> listener); + } + + private static final class ResultPromises { + final CompletionStage result; + final CompletionStage resultMetadata; + + ResultPromises(CompletionStage result, CompletionStage resultMetadata) { + this.result = result; + this.resultMetadata = resultMetadata; + } + } + + @SuppressWarnings("unchecked") + private ResultPromises createResultPromises(String requestUuid, AppIdentifier source) { + CompletableFuture metadataFuture = new CompletableFuture<>(); + CompletionStage result = messaging.>waitFor( + m -> { + String type = (String) m.get("type"); + Map meta = (Map) m.get("meta"); + String respRequestUuid = meta != null ? (String) meta.get("requestUuid") : null; + return "raiseIntentResultResponse".equals(type) && requestUuid.equals(respRequestUuid); + }, + 0, + null + ).thenApply(response -> { + metadataFuture.complete(extractResultMetadata(response, source)); + Map payload = (Map) response.get("payload"); + return convertIntentResult(payload.get("intentResult")); + }); + return new ResultPromises(result, metadataFuture); + } + + @SuppressWarnings("unchecked") + private static ContextMetadata extractResultMetadata(Map response, AppIdentifier source) { + Map payload = (Map) response.get("payload"); + Map resultMetadata = payload != null + ? (Map) payload.get("resultMetadata") + : null; + ContextMetadata metadata = ContextMetadataMapper.fromWire(resultMetadata, null); + metadata.setSource(source); + return metadata; + } + + @SuppressWarnings("unchecked") + private IntentResult convertIntentResult(Object intentResultObj) { + if (intentResultObj == null) { + return null; + } + if (!(intentResultObj instanceof Map)) { + return null; + } + Map intentResult = (Map) intentResultObj; + if (intentResult.isEmpty()) { + return null; + } + Object contextObj = intentResult.get("context"); + if (contextObj != null) { + if (contextObj instanceof Context) { + return (Context) contextObj; + } + return Context.fromMap((Map) contextObj); + } + Object channelObj = intentResult.get("channel"); + if (channelObj instanceof Map) { + return createChannel((Map) channelObj); + } + return null; + } + + @SuppressWarnings("unchecked") + private Channel createChannel(Map channelMap) { + String id = (String) channelMap.get("id"); + Object typeObj = channelMap.get("type"); + Channel.Type type = Channel.Type.App; + if (typeObj != null) { + String typeStr = typeObj.toString(); + if ("user".equals(typeStr)) { + type = Channel.Type.User; + } else if ("private".equals(typeStr)) { + type = Channel.Type.Private; + } + } + Map displayMetadataMap = (Map) channelMap.get("displayMetadata"); + org.finos.fdc3.api.metadata.DisplayMetadata displayMetadata = null; + if (displayMetadataMap != null) { + displayMetadata = org.finos.fdc3.api.metadata.DisplayMetadata.fromMap(displayMetadataMap); + } + if (type == Channel.Type.Private) { + return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); + } + return new DefaultChannel(messaging, messageExchangeTimeout, id, type, displayMetadata); + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java new file mode 100644 index 00000000..23a8b2c7 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java @@ -0,0 +1,103 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.intents; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.Listener; + +/** + * Interface for intent-related operations. + */ +public interface IntentSupport { + + /** + * Find applications that can handle a specific intent. + * + * @param intent the intent name + * @param context optional context to filter by + * @param resultType optional result type to filter by + * @return a CompletionStage containing the app intent information + */ + CompletionStage findIntent(String intent, Context context, String resultType); + + /** + * Find intents that can handle a specific context. + * + * @param context the context to find intents for + * @return a CompletionStage containing the list of app intents + */ + CompletionStage> findIntentsByContext(Context context); + + /** + * Raise an intent. + * + * @param intent the intent name + * @param context the context to pass + * @param app optional target application + * @return a CompletionStage containing the intent resolution + */ + CompletionStage raiseIntent(String intent, Context context, AppIdentifier app); + + /** + * Raise an intent with app-provided metadata. + * + * @param intent the intent name + * @param context the context to pass + * @param app optional target application + * @param metadata optional app-provided metadata + * @return a CompletionStage containing the intent resolution + */ + CompletionStage raiseIntent( + String intent, Context context, AppIdentifier app, AppProvidableContextMetadata metadata); + + /** + * Raise an intent for a context. + * + * @param context the context + * @param app optional target application + * @return a CompletionStage containing the intent resolution + */ + CompletionStage raiseIntentForContext(Context context, AppIdentifier app); + + /** + * Raise an intent for a context with app-provided metadata. + * + * @param context the context + * @param app optional target application + * @param metadata optional app-provided metadata + * @return a CompletionStage containing the intent resolution + */ + CompletionStage raiseIntentForContext( + Context context, AppIdentifier app, AppProvidableContextMetadata metadata); + + /** + * Add an intent listener. + * + * @param intent the intent to listen for + * @param handler the intent handler + * @return a CompletionStage containing the listener + */ + CompletionStage addIntentListener(String intent, IntentHandler handler); +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractListener.java new file mode 100644 index 00000000..632426ad --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractListener.java @@ -0,0 +1,152 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Map; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; + +/** + * Common base for all listeners - handles registration and unregistration with messaging + * and sends notification messages when connected and disconnected. + * + * This mirrors the TypeScript AbstractListener pattern. + * + * @param The handler type (e.g., ContextHandler, IntentHandler, EventHandler) + */ +public abstract class AbstractListener implements RegisterableListener, Listener { + + protected final Messaging messaging; + protected final long messageExchangeTimeout; + protected final H handler; + + private final String subscribeRequestType; + private final String subscribeResponseType; + private final String unsubscribeRequestType; + private final String unsubscribeResponseType; + + // The listener ID assigned by the server + protected String id = null; + + /** + * Construct an AbstractListener. + * + * @param messaging the messaging system + * @param messageExchangeTimeout timeout for message exchanges + * @param handler the handler callback + * @param subscribeRequestType the type string for subscribe requests (e.g., "addContextListenerRequest") + * @param subscribeResponseType the type string for subscribe responses (e.g., "addContextListenerResponse") + * @param unsubscribeRequestType the type string for unsubscribe requests (e.g., "contextListenerUnsubscribeRequest") + * @param unsubscribeResponseType the type string for unsubscribe responses (e.g., "contextListenerUnsubscribeResponse") + */ + protected AbstractListener( + Messaging messaging, + long messageExchangeTimeout, + H handler, + String subscribeRequestType, + String subscribeResponseType, + String unsubscribeRequestType, + String unsubscribeResponseType) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.handler = handler; + this.subscribeRequestType = subscribeRequestType; + this.subscribeResponseType = subscribeResponseType; + this.unsubscribeRequestType = unsubscribeRequestType; + this.unsubscribeResponseType = unsubscribeResponseType; + } + + @Override + public String getId() { + return id; + } + + /** + * Filter function to determine if a message should be processed. + * Subclasses implement this to define their filtering logic. + * + * @param message the incoming message + * @return true if the message should be processed + */ + @Override + public abstract boolean filter(Map message); + + /** + * Action to perform when a matching message is received. + * Subclasses implement this to define their handling logic. + * + * @param message the matched message + */ + @Override + public abstract void action(Map message); + + /** + * Build the subscription request payload. + * Subclasses implement this to provide their specific payload. + * + * @return the subscription request as a Map + */ + protected abstract Map buildSubscribeRequest(); + + @Override + @SuppressWarnings("unchecked") + public CompletionStage register() { + Map request = buildSubscribeRequest(); + request.put("type", subscribeRequestType); + request.put("meta", messaging.getConverter().toMap(messaging.createMeta())); + + return messaging.>exchange(request, subscribeResponseType, messageExchangeTimeout) + .thenAccept(response -> { + // Extract listenerUUID from the response + Map payload = (Map) response.get("payload"); + if (payload != null) { + this.id = (String) payload.get("listenerUUID"); + } + + if (this.id == null) { + throw new RuntimeException( + "The Desktop Agent's response did not include a listenerUUID, " + + "which means this listener can't be removed!"); + } + + messaging.register(this); + }); + } + + @Override + public CompletionStage unsubscribe() { + if (this.id != null) { + messaging.unregister(this.id); + + Map request = new java.util.HashMap<>(); + request.put("type", unsubscribeRequestType); + request.put("meta", messaging.getConverter().toMap(messaging.createMeta())); + + Map payload = new java.util.HashMap<>(); + payload.put("listenerUUID", this.id); + request.put("payload", payload); + + return messaging.>exchange(request, unsubscribeResponseType, messageExchangeTimeout) + .thenAccept(response -> { /* completed */ }); + } else { + throw new RuntimeException("This listener doesn't have an id and hence can't be removed!"); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java new file mode 100644 index 00000000..cd56cffd --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java @@ -0,0 +1,106 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.PrivateChannelEventType; + +/** + * Abstract base class for private channel event listeners. + * Extends AbstractListener to handle registration/unregistration. + */ +public abstract class AbstractPrivateChannelEventListener extends AbstractListener { + + protected final String privateChannelId; + protected final List eventMessageTypes; + protected final String eventType; + + protected AbstractPrivateChannelEventListener( + Messaging messaging, + long messageExchangeTimeout, + String privateChannelId, + List eventMessageTypes, + String eventType, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + handler, + "privateChannelAddEventListenerRequest", + "privateChannelAddEventListenerResponse", + "privateChannelUnsubscribeEventListenerRequest", + "privateChannelUnsubscribeEventListenerResponse" + ); + this.privateChannelId = privateChannelId; + this.eventMessageTypes = eventMessageTypes; + this.eventType = eventType; + } + + @Override + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + payload.put("privateChannelId", privateChannelId); + PrivateChannelEventType pcEventType = toPrivateChannelEventType(eventType); + // Explicitly set listenerType to null if eventType is null, otherwise use the enum value + payload.put("listenerType", pcEventType != null ? pcEventType.toValue() : null); + request.put("payload", payload); + return request; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!eventMessageTypes.contains(type)) { + return false; + } + + Map payload = (Map) message.get("payload"); + if (payload == null) { + return false; + } + + String msgChannelId = (String) payload.get("privateChannelId"); + return privateChannelId.equals(msgChannelId); + } + + @Override + public abstract void action(Map message); + + private PrivateChannelEventType toPrivateChannelEventType(String eventType) { + if (eventType == null) { + return null; + } + switch (eventType) { + case "addContextListener": + return PrivateChannelEventType.ADD_CONTEXT_LISTENER; + case "unsubscribe": + return PrivateChannelEventType.UNSUBSCRIBE; + case "disconnect": + return PrivateChannelEventType.DISCONNECT; + default: + throw new RuntimeException("Unsupported event type: " + eventType); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java new file mode 100644 index 00000000..9db03234 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java @@ -0,0 +1,137 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.ContextMetadataMapper; + +/** + * Default implementation of a context listener. + * Extends AbstractListener to handle registration/unregistration. + */ +public class DefaultContextListener extends AbstractListener { + + protected String channelId; + protected final String contextType; + protected final String messageType; + + public DefaultContextListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + String contextType, + ContextHandler handler) { + this(messaging, messageExchangeTimeout, channelId, contextType, handler, "broadcastEvent"); + } + + public DefaultContextListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + String contextType, + ContextHandler handler, + String messageType) { + super( + messaging, + messageExchangeTimeout, + handler, + "addContextListenerRequest", + "addContextListenerResponse", + "contextListenerUnsubscribeRequest", + "contextListenerUnsubscribeResponse" + ); + this.channelId = channelId; + this.contextType = contextType; + this.messageType = messageType; + } + + /** + * Update the channel this listener is listening to. This is used for non-user + * channel listeners (e.g., app channels, private channels). + * + * @param channel the new channel to listen to + */ + public void changeChannel(Channel channel) { + if (channel == null) { + this.channelId = null; + } else { + this.channelId = channel.getId(); + // Get current context from the channel + channel.getCurrentContextWithMetadata(contextType) + .thenAccept(result -> { + result.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); + }); + } + } + + @Override + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + payload.put("channelId", channelId); + payload.put("contextType", contextType); + request.put("payload", payload); + return request; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!messageType.equals(type)) { + return false; + } + + Map payload = (Map) message.get("payload"); + if (payload == null) { + return false; + } + + String msgChannelId = (String) payload.get("channelId"); + if (channelId != null && !channelId.equals(msgChannelId)) { + return false; + } + + Map context = (Map) payload.get("context"); + if (context == null) { + return false; + } + + String msgContextType = (String) context.get("type"); + return contextType == null || contextType.equals(msgContextType); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + Map payload = (Map) message.get("payload"); + Map contextMap = (Map) payload.get("context"); + Context context = Context.fromMap(contextMap); + Map messageMeta = (Map) message.get("meta"); + Map payloadMetadata = (Map) payload.get("metadata"); + Object messageTimestamp = messageMeta != null ? messageMeta.get("timestamp") : null; + ContextMetadata metadata = ContextMetadataMapper.fromWire(payloadMetadata, messageTimestamp, messageMeta); + handler.handleContext(context, metadata); + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java new file mode 100644 index 00000000..cfb000c9 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java @@ -0,0 +1,239 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.ContextWithMetadata; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.ContextMetadataMapper; +import org.finos.fdc3.schema.IntentEvent; +import org.finos.fdc3.schema.IntentResultRequest; +import org.finos.fdc3.schema.IntentResultRequestPayload; +import org.finos.fdc3.schema.IntentResultRequestType; +import org.finos.fdc3.schema.IntentResultResponse; + +/** + * Default implementation of an intent listener. + * Extends AbstractListener to handle registration/unregistration. + */ +public class DefaultIntentListener extends AbstractListener { + + private final String intent; + + public DefaultIntentListener( + Messaging messaging, + String intent, + IntentHandler handler, + long messageExchangeTimeout) { + super( + messaging, + messageExchangeTimeout, + handler, + "addIntentListenerRequest", + "addIntentListenerResponse", + "intentListenerUnsubscribeRequest", + "intentListenerUnsubscribeResponse" + ); + this.intent = intent; + } + + @Override + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + payload.put("intent", intent); + request.put("payload", payload); + return request; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!"intentEvent".equals(type)) { + return false; + } + + Map payload = (Map) message.get("payload"); + if (payload == null) { + return false; + } + + String msgIntent = (String) payload.get("intent"); + return intent.equals(msgIntent); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + // Convert the message to typed IntentEvent + IntentEvent intentEvent = messaging.getConverter().convertValue(message, IntentEvent.class); + + Context context = intentEvent.getPayload().getContext(); + Map messageMap = message; + Map payloadMap = (Map) messageMap.get("payload"); + Map payloadMetadata = payloadMap != null + ? (Map) payloadMap.get("metadata") + : null; + Object messageTimestamp = messageMap.get("meta") != null + ? ((Map) messageMap.get("meta")).get("timestamp") + : null; + ContextMetadata contextMetadata = ContextMetadataMapper.fromWire(payloadMetadata, messageTimestamp); + + CompletionStage> resultFuture = handler.handleIntent(context, contextMetadata); + handleIntentResult(resultFuture, intentEvent); + } + + private void handleIntentResult( + CompletionStage> resultFuture, + IntentEvent intentEvent) { + + resultFuture.thenAccept(optionalResult -> { + UnwrappedIntentResult unwrapped = unwrapIntentResult(optionalResult.orElse(null)); + IntentResultRequest request = createIntentResultRequest( + unwrapped.result, unwrapped.appMetadata, intentEvent); + + Map requestMap = messaging.getConverter().toMap(request); + if (unwrapped.appMetadata != null) { + @SuppressWarnings("unchecked") + Map payload = (Map) requestMap.get("payload"); + if (payload != null) { + payload.put("metadata", ContextMetadataMapper.toWire(unwrapped.appMetadata)); + } + } + + messaging.>exchange( + requestMap, + "intentResultResponse", + messageExchangeTimeout + ).exceptionally(ex -> { + // Log error but don't fail + System.err.println("Failed to send intent result: " + ex.getMessage()); + return null; + }); + }).exceptionally(ex -> { + // Handler threw an exception, send empty result + IntentResultRequest request = createIntentResultRequest(null, null, intentEvent); + Map requestMap = messaging.getConverter().toMap(request); + + messaging.>exchange( + requestMap, + "intentResultResponse", + messageExchangeTimeout + ).exceptionally(ex2 -> { + System.err.println("Failed to send intent result after error: " + ex2.getMessage()); + return null; + }); + return null; + }); + } + + private static final class UnwrappedIntentResult { + final IntentResult result; + final AppProvidableContextMetadata appMetadata; + + UnwrappedIntentResult(IntentResult result, AppProvidableContextMetadata appMetadata) { + this.result = result; + this.appMetadata = appMetadata; + } + } + + private static UnwrappedIntentResult unwrapIntentResult(Object raw) { + if (raw instanceof ContextWithMetadata) { + ContextWithMetadata cwm = (ContextWithMetadata) raw; + return new UnwrappedIntentResult(cwm.getContext(), cwm.getMetadata()); + } + if (raw instanceof IntentResult) { + return new UnwrappedIntentResult((IntentResult) raw, null); + } + return new UnwrappedIntentResult(null, null); + } + + private IntentResultRequest createIntentResultRequest( + IntentResult apiResult, + AppProvidableContextMetadata appMetadata, + IntentEvent intentEvent) { + + IntentResultRequest request = new IntentResultRequest(); + request.setType(IntentResultRequestType.INTENT_RESULT_REQUEST); + + org.finos.fdc3.schema.AddContextListenerRequestMeta meta = + new org.finos.fdc3.schema.AddContextListenerRequestMeta(); + meta.setRequestUUID(intentEvent.getMeta().getEventUUID()); + meta.setTimestamp(OffsetDateTime.now()); + request.setMeta(meta); + + IntentResultRequestPayload payload = new IntentResultRequestPayload(); + payload.setIntentEventUUID(intentEvent.getMeta().getEventUUID()); + payload.setRaiseIntentRequestUUID(intentEvent.getPayload().getRaiseIntentRequestUUID()); + payload.setIntentResult(convertIntentResult(apiResult)); + request.setPayload(payload); + + return request; + } + + private org.finos.fdc3.schema.IntentResult convertIntentResult(IntentResult apiResult) { + org.finos.fdc3.schema.IntentResult schemaResult = new org.finos.fdc3.schema.IntentResult(); + + if (apiResult == null) { + // Void result - return empty IntentResult + return schemaResult; + } + + if (apiResult instanceof Context) { + schemaResult.setContext((Context) apiResult); + } else if (apiResult instanceof Channel) { + Channel channel = (Channel) apiResult; + org.finos.fdc3.schema.Channel schemaChannel = new org.finos.fdc3.schema.Channel(); + schemaChannel.setID(channel.getId()); + schemaChannel.setType(convertChannelType(channel.getType())); + // DisplayMetadata is not part of the Channel API interface, + // only available on UserChannel/PrivateChannel implementations + schemaChannel.setDisplayMetadata(null); + schemaResult.setChannel(schemaChannel); + } + + return schemaResult; + } + + private org.finos.fdc3.schema.Type convertChannelType(Channel.Type type) { + if (type == null) { + return null; + } + switch (type) { + case User: + return org.finos.fdc3.schema.Type.USER; + case App: + return org.finos.fdc3.schema.Type.APP; + case Private: + return org.finos.fdc3.schema.Type.PRIVATE; + default: + return null; + } + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java new file mode 100644 index 00000000..4eb7d445 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java @@ -0,0 +1,147 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.FDC3EventType; + +/** + * Listener for Desktop Agent events. + * Extends AbstractListener to handle registration/unregistration. + */ +public class DesktopAgentEventListener extends AbstractListener { + + private final String eventType; + + public DesktopAgentEventListener( + Messaging messaging, + long messageExchangeTimeout, + String eventType, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + handler, + "addEventListenerRequest", + "addEventListenerResponse", + "eventListenerUnsubscribeRequest", + "eventListenerUnsubscribeResponse" + ); + validateEventType(eventType); + this.eventType = eventType; + } + + /** + * Validates that the event type is supported. + * Throws RuntimeException with "UnknownEventType" if not supported. + */ + private static void validateEventType(String eventType) { + if (eventType == null) { + // null is allowed (listen to all events) + return; + } + switch (eventType) { + case "userChannelChanged": + // Valid event type + return; + default: + throw new RuntimeException("UnknownEventType"); + } + } + + @Override + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + FDC3EventType fdc3EventType = toFDC3SchemaEventType(eventType); + // Explicitly set type to null if eventType is null, otherwise use the enum value + payload.put("type", fdc3EventType != null ? fdc3EventType.toValue() : null); + request.put("payload", payload); + return request; + } + + @Override + public boolean filter(Map message) { + String messageType = (String) message.get("type"); + if (messageType == null) { + return false; + } + if (eventType == null) { + // Wildcard listeners receive agent events only, not request/response traffic. + return !messageType.endsWith("Response") && !messageType.endsWith("Request"); + } + return getExpectedMessageType().equals(messageType); + } + + /** + * Maps FDC3 event types to their corresponding message types. + * e.g., "userChannelChanged" -> "channelChangedEvent" + */ + private String getExpectedMessageType() { + if (eventType == null) { + return null; + } + switch (eventType) { + case "userChannelChanged": + return "channelChangedEvent"; + default: + throw new RuntimeException("UnknownEventType"); + } + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + String messageType = (String) message.get("type"); + Map payload = (Map) message.get("payload"); + + FDC3Event.Type eventType = FDC3Event.Type.fromMessageType(messageType); + Object details = buildEventDetails(messageType, payload); + handler.handleEvent(new FDC3Event(eventType, details)); + } + + private static Object buildEventDetails(String messageType, Map payload) { + if ("channelChangedEvent".equals(messageType)) { + Map details = new HashMap<>(); + Object currentChannelId = payload.get("currentChannelId"); + if (currentChannelId == null) { + currentChannelId = payload.get("newChannelId"); + } + details.put("currentChannelId", currentChannelId); + return details; + } + return payload; + } + + private FDC3EventType toFDC3SchemaEventType(String eventType) { + if (eventType == null) { + return null; + } + switch (eventType) { + case "userChannelChanged": + return FDC3EventType.USER_CHANNEL_CHANGED; + default: + throw new RuntimeException("UnknownEventType"); + } + } + +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelAddContextEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelAddContextEventListener.java new file mode 100644 index 00000000..73eb6f36 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelAddContextEventListener.java @@ -0,0 +1,65 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; + +/** + * Listener for private channel addContextListener events. + */ +public class PrivateChannelAddContextEventListener extends AbstractPrivateChannelEventListener { + + public PrivateChannelAddContextEventListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + channelId, + Arrays.asList("privateChannelOnAddContextListenerEvent"), + "addContextListener", + handler + ); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + String type = (String) message.get("type"); + + if ("privateChannelOnAddContextListenerEvent".equals(type)) { + Map payload = (Map) message.get("payload"); + Map details = new HashMap<>(); + details.put("contextType", payload.get("contextType")); + + FDC3Event event = new FDC3Event(FDC3Event.Type.ADD_CONTEXT_LISTENER, details); + handler.handleEvent(event); + } else { + Logger.error("PrivateChannelAddContextEventListener was called for a different message type: " + type); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelDisconnectEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelDisconnectEventListener.java new file mode 100644 index 00000000..ccb9c85d --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelDisconnectEventListener.java @@ -0,0 +1,60 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Arrays; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; + +/** + * Listener for private channel disconnect events. + */ +public class PrivateChannelDisconnectEventListener extends AbstractPrivateChannelEventListener { + + public PrivateChannelDisconnectEventListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + channelId, + Arrays.asList("privateChannelOnDisconnectEvent"), + "disconnect", + handler + ); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + String type = (String) message.get("type"); + + if ("privateChannelOnDisconnectEvent".equals(type)) { + FDC3Event event = new FDC3Event(FDC3Event.Type.ON_DISCONNECT, null); + handler.handleEvent(event); + } else { + Logger.error("PrivateChannelDisconnectEventListener was called for a different message type: " + type); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelNullEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelNullEventListener.java new file mode 100644 index 00000000..e9dc78c4 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelNullEventListener.java @@ -0,0 +1,85 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; + +/** + * Listener for all private channel events (null event type). + */ +public class PrivateChannelNullEventListener extends AbstractPrivateChannelEventListener { + + public PrivateChannelNullEventListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + channelId, + Arrays.asList( + "privateChannelOnAddContextListenerEvent", + "privateChannelOnUnsubscribeEvent", + "privateChannelOnDisconnectEvent" + ), + null, // eventType is null for listening to all events + handler + ); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + String type = (String) message.get("type"); + Map payload = (Map) message.get("payload"); + + FDC3Event.Type eventType; + Map details; + + switch (type) { + case "privateChannelOnAddContextListenerEvent": + eventType = FDC3Event.Type.ADD_CONTEXT_LISTENER; + details = new HashMap<>(); + details.put("contextType", payload.get("contextType")); + break; + case "privateChannelOnUnsubscribeEvent": + eventType = FDC3Event.Type.ON_UNSUBSCRIBE; + details = new HashMap<>(); + details.put("contextType", payload.get("contextType")); + break; + case "privateChannelOnDisconnectEvent": + eventType = FDC3Event.Type.ON_DISCONNECT; + details = null; + break; + default: + Logger.error("PrivateChannelNullEventListener received unexpected message type: " + type); + return; + } + + FDC3Event event = new FDC3Event(eventType, details); + handler.handleEvent(event); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelUnsubscribeEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelUnsubscribeEventListener.java new file mode 100644 index 00000000..fa9c671e --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelUnsubscribeEventListener.java @@ -0,0 +1,65 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; + +/** + * Listener for private channel unsubscribe events. + */ +public class PrivateChannelUnsubscribeEventListener extends AbstractPrivateChannelEventListener { + + public PrivateChannelUnsubscribeEventListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + EventHandler handler) { + super( + messaging, + messageExchangeTimeout, + channelId, + Arrays.asList("privateChannelOnUnsubscribeEvent"), + "unsubscribe", + handler + ); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + String type = (String) message.get("type"); + + if ("privateChannelOnUnsubscribeEvent".equals(type)) { + Map payload = (Map) message.get("payload"); + Map details = new HashMap<>(); + details.put("contextType", payload.get("contextType")); + + FDC3Event event = new FDC3Event(FDC3Event.Type.ON_UNSUBSCRIBE, details); + handler.handleEvent(event); + } else { + Logger.error("PrivateChannelUnsubscribeEventListener was called for a different message type: " + type); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java new file mode 100644 index 00000000..2c8f12a4 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java @@ -0,0 +1,65 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.listeners; + +import java.util.Map; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.types.Listener; + +/** + * A listener that can be registered with the messaging system. + */ +public interface RegisterableListener extends Listener { + + /** + * Get the unique identifier for this listener. + * + * @return the listener ID + */ + String getId(); + + /** + * Filter function to determine if a message should be processed. + * + * @param message the incoming message + * @return true if the message should be processed + */ + boolean filter(Map message); + + /** + * Action to perform when a matching message is received. + * + * @param message the matched message + */ + void action(Map message); + + /** + * Register this listener with the messaging system. + * + * @return a CompletionStage that completes when registered + */ + CompletionStage register(); + + /** + * Unsubscribe this listener from the messaging system. + * + * @return a CompletionStage that completes when unsubscribed + */ + CompletionStage unsubscribe(); +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java new file mode 100644 index 00000000..4e9837f9 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java @@ -0,0 +1,211 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.messaging; + +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.AddContextListenerRequestMeta; +import org.finos.fdc3.schema.SchemaConverter; + +/** + * Abstract base class for messaging implementations. + */ +public abstract class AbstractMessaging implements Messaging { + + private static final String API_TIMEOUT = "ApiTimeout"; + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + private AppIdentifier appIdentifier; + private String instanceUuid; + private final SchemaConverter converter; + + protected AbstractMessaging(AppIdentifier appIdentifier) { + this.appIdentifier = appIdentifier; + this.converter = new SchemaConverter(); + } + + /** + * Sets the identity for this messaging instance after validation. + * This is called after the handshake when the Desktop Agent provides + * the validated AppIdentifier and instanceUuid. + * + * @param appIdentifier the validated app identifier + * @param instanceUuid the instance UUID (shared secret for reconnection) + */ + public void setIdentifier(AppIdentifier appIdentifier, String instanceUuid) { + this.appIdentifier = appIdentifier; + this.instanceUuid = instanceUuid; + } + + @Override + public String getInstanceUuid() { + return instanceUuid; + } + + @Override + public abstract String createUUID(); + + @Override + public abstract CompletionStage post(Map message); + + @Override + public abstract void register(RegisterableListener listener); + + @Override + public abstract void unregister(String id); + + @Override + public AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + if (appIdentifier != null) { + // Create a copy with desktopAgent set + AppIdentifier source = new AppIdentifier( + appIdentifier.getAppId(), + appIdentifier.getInstanceId(), + "testing-da" + ); + meta.setSource(source); + } + return meta; + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage waitFor(Predicate filter, long timeoutMs, String timeoutErrorMessage) { + String id = createUUID(); + CompletableFuture future = new CompletableFuture<>(); + + final ScheduledFuture[] timeoutFuture = new ScheduledFuture[1]; + + RegisterableListener listener = new RegisterableListener() { + @Override + public String getId() { + return id; + } + + @Override + public boolean filter(Map message) { + try { + return filter.test((X) message); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public void action(Map message) { + Logger.debug("Received from DesktopAgent: {}", message); + unregister(id); + if (timeoutFuture[0] != null) { + timeoutFuture[0].cancel(false); + } + future.complete((X) message); + } + + @Override + public CompletionStage register() { + AbstractMessaging.this.register(this); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage unsubscribe() { + AbstractMessaging.this.unregister(id); + return CompletableFuture.completedFuture(null); + } + }; + + register(listener); + + if (timeoutMs > 0) { + timeoutFuture[0] = scheduler.schedule(() -> { + unregister(id); + if (!future.isDone()) { + Logger.error("waitFor rejecting after {}ms with {}", timeoutMs, timeoutErrorMessage); + future.completeExceptionally(new RuntimeException(timeoutErrorMessage)); + } + }, timeoutMs, TimeUnit.MILLISECONDS); + } + + return future; + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage exchange(Map message, String expectedTypeName, long timeoutMs) { + Map meta = (Map) message.get("meta"); + String requestUuid = (String) meta.get("requestUuid"); + + CompletionStage promise = waitFor( + m -> { + Map msg = (Map) m; + String type = (String) msg.get("type"); + Map msgMeta = (Map) msg.get("meta"); + String respRequestUuid = msgMeta != null ? (String) msgMeta.get("requestUuid") : null; + return expectedTypeName.equals(type) && requestUuid.equals(respRequestUuid); + }, + timeoutMs, + API_TIMEOUT + ); + + Logger.debug("Sending to DesktopAgent: {}", message); + + // Wait for post to complete before proceeding to ensure message is recorded + return post(message).thenCompose(v -> promise).thenApply(response -> { + Map resp = (Map) response; + Map payload = (Map) resp.get("payload"); + if (payload != null && payload.get("error") != null) { + throw new RuntimeException((String) payload.get("error")); + } + return response; + }).exceptionally(error -> { + if (API_TIMEOUT.equals(error.getMessage())) { + Logger.error("Timed-out while waiting for {} with requestUuid {}", expectedTypeName, requestUuid); + } + throw new RuntimeException(error); + }); + } + + @Override + public AppIdentifier getAppIdentifier() { + return appIdentifier; + } + + @Override + public SchemaConverter getConverter() { + return converter; + } + + @Override + public abstract CompletionStage disconnect(); +} + diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java new file mode 100644 index 00000000..5ff63391 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java @@ -0,0 +1,160 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.util; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +import org.finos.fdc3.api.metadata.AntiReplayClaims; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.DetachedSignature; +import org.finos.fdc3.api.types.AppIdentifier; + +/** + * Maps between DACP wire metadata maps and {@link ContextMetadata}. + */ +public final class ContextMetadataMapper { + + private ContextMetadataMapper() { + } + + /** + * Outbound metadata for DACP request payloads (reference TS: {@code metadata ?? {}}). + */ + public static Map toWire(AppProvidableContextMetadata metadata) { + return toWire(metadata, false, null); + } + + /** + * Outbound metadata for intent raise requests (reference TS: always includes {@code traceId}). + */ + public static Map toWireForIntentRequest( + AppProvidableContextMetadata metadata, + Supplier traceIdSupplier) { + return toWire(metadata, true, traceIdSupplier); + } + + private static Map toWire( + AppProvidableContextMetadata metadata, + boolean ensureTraceId, + Supplier traceIdSupplier) { + if (metadata == null) { + if (ensureTraceId && traceIdSupplier != null) { + Map wire = new LinkedHashMap<>(); + wire.put("traceId", traceIdSupplier.get()); + return wire; + } + return new LinkedHashMap<>(); + } + if (!(metadata instanceof ContextMetadata)) { + throw new IllegalArgumentException("metadata must be ContextMetadata"); + } + ContextMetadata cm = (ContextMetadata) metadata; + Map wire = new LinkedHashMap<>(); + + String traceId = cm.getTraceId(); + if (traceId == null && ensureTraceId && traceIdSupplier != null) { + traceId = traceIdSupplier.get(); + } + if (traceId != null) { + wire.put("traceId", traceId); + } + + DetachedSignature signature = cm.getSignature(); + if (signature != null) { + wire.put("signature", signatureToMap(signature)); + } + + AntiReplayClaims antiReplay = cm.getAntiReplay(); + if (antiReplay != null) { + wire.put("antiReplay", antiReplayToMap(antiReplay)); + } + + Map custom = cm.getCustom(); + if (custom != null) { + wire.put("custom", new LinkedHashMap<>(custom)); + } + + return wire; + } + + public static ContextMetadata fromWire(Map payloadMetadata, Object messageTimestamp) { + return fromWire(payloadMetadata, messageTimestamp, null); + } + + /** + * Builds listener metadata from wire payload fields and optional message {@code meta} (e.g. event source). + */ + @SuppressWarnings("unchecked") + public static ContextMetadata fromWire( + Map payloadMetadata, + Object messageTimestamp, + Map messageMeta) { + ContextMetadata metadata = ContextMetadata.fromMap(payloadMetadata); + if (metadata == null) { + metadata = ContextMetadata.appProvidable(); + } + if (metadata.getTimestamp() == null && messageTimestamp != null) { + if (messageTimestamp instanceof Instant) { + metadata.setTimestamp((Instant) messageTimestamp); + } else { + metadata.setTimestamp(Instant.parse(String.valueOf(messageTimestamp))); + } + } + if (metadata.getTraceId() == null || metadata.getTraceId().isEmpty()) { + metadata.setTraceId(UUID.randomUUID().toString()); + } + applyMetaSourceIfAbsent(metadata, messageMeta); + return metadata; + } + + @SuppressWarnings("unchecked") + private static void applyMetaSourceIfAbsent(ContextMetadata metadata, Map messageMeta) { + if (metadata.getSource() != null || messageMeta == null) { + return; + } + Object source = messageMeta.get("source"); + if (source instanceof AppIdentifier) { + metadata.setSource((AppIdentifier) source); + } else if (source instanceof Map) { + metadata.setSource(AppIdentifier.fromMap((Map) source)); + } + } + + private static Map signatureToMap(DetachedSignature signature) { + Map map = new LinkedHashMap<>(); + if (signature.getProtectedHeader() != null) { + map.put("protected", signature.getProtectedHeader()); + } + if (signature.getSignature() != null) { + map.put("signature", signature.getSignature()); + } + return map; + } + + private static Map antiReplayToMap(AntiReplayClaims antiReplay) { + Map map = new LinkedHashMap<>(); + map.put("iat", antiReplay.getIat()); + map.put("exp", antiReplay.getExp()); + map.put("jti", antiReplay.getJti()); + return map; + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java new file mode 100644 index 00000000..724c3df6 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java @@ -0,0 +1,69 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.util; + +import org.slf4j.LoggerFactory; + +/** + * Simple logging utility for the FDC3 Agent Proxy. + *

+ * Log levels are configured through the SLF4J binding (e.g., Logback, Log4j2). + */ +public final class Logger { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger("fdc3.proxy"); + + private Logger() { + // Utility class + } + + public static void debug(String message) { + log.debug(message); + } + + public static void debug(String message, Object... args) { + log.debug(message, args); + } + + public static void info(String message) { + log.info(message); + } + + public static void info(String message, Object... args) { + log.info(message, args); + } + + public static void warn(String message) { + log.warn(message); + } + + public static void warn(String message, Object... args) { + log.warn(message, args); + } + + public static void error(String message) { + log.error(message); + } + + public static void error(String message, Object... args) { + log.error(message, args); + } + + public static void error(String message, Throwable throwable) { + log.error(message, throwable); + } +} diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Country.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java similarity index 50% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Country.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java index 45f5b56e..a1b05149 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/context/Country.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +14,29 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.proxy; -import java.util.Map; +import io.cucumber.java.Before; +import io.cucumber.java.Scenario; +import org.finos.fdc3.proxy.world.CustomWorld; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +/** + * Cucumber hooks for setting up and tearing down test state. + */ +public class CucumberHooks { -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Country extends Context { - public static String TYPE = "fdc3.country"; - public static Map VALID_ID_KEYS = Map.of("COUNTRY_ISOALPHA2", true, "COUNTRY_ISOALPHA3", false, "ISOALPHA2", false, "ISOALPHA3", false); + private final CustomWorld world; - public Country(Map id, String name) { - super(TYPE, name, id); + public CucumberHooks(CustomWorld world) { + this.world = world; } - public Country(Map id) { - super(TYPE, id); + /** + * Before each scenario, set the scenario on the world for logging. + */ + @Before + public void beforeScenario(Scenario scenario) { + world.setScenario(scenario); } } + diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentMetadata.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java similarity index 60% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentMetadata.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java index 6297789f..d59c1158 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentMetadata.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.proxy; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.test.context.ContextConfiguration; /** - * Intent descriptor + * Cucumber Spring configuration entry point. */ -public interface IntentMetadata { - /** The unique name of the intent that can be invoked by the raiseIntent call */ - public String getName(); - - /** A friendly display name for the intent that should be used to render UI elements */ - public String getDisplayName(); +@CucumberContextConfiguration +@ContextConfiguration(classes = TestSpringConfig.class) +public class CucumberSpringConfiguration { } diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java new file mode 100644 index 00000000..05cd3ef8 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java @@ -0,0 +1,43 @@ +/* + * Copyright FINOS and Contributors to the FDC3 project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.finos.fdc3.proxy; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.Suite; + +import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +/** + * JUnit Platform Suite entry point so Maven Surefire discovers Cucumber scenarios, + * reports an accurate test count, and fails the build on scenario failures. + * + * @see cucumber-junit-platform-engine + */ +@Suite +@IncludeEngines("cucumber") +@ConfigurationParameter(key = FEATURES_PROPERTY_NAME, value = "classpath:temporary-features") +@ConfigurationParameter( + key = GLUE_PROPERTY_NAME, + value = "org.finos.fdc3.proxy,org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,io.github.robmoffat.steps") +@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.spring.SpringFactory") +@ConfigurationParameter( + key = PLUGIN_PROPERTY_NAME, + value = "pretty,summary,junit:target/cucumber-reports/cucumber.xml,html:target/cucumber-reports/cucumber.html") +public class RunCucumberTest {} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/TestSpringConfig.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/TestSpringConfig.java new file mode 100644 index 00000000..b586b3bb --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/TestSpringConfig.java @@ -0,0 +1,64 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy; + +import org.finos.fdc3.proxy.world.CustomWorld; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.ScopedProxyMode; + +import io.cucumber.spring.ScenarioScope; +import io.github.robmoffat.world.PropsWorld; + +/** + * Spring configuration for Cucumber tests. + * + * This configuration ensures that: + * 1. CustomWorld is used as the shared world instance + * 2. PropsWorld injections receive the CustomWorld instance + */ +@Configuration +@ComponentScan(basePackages = { + "org.finos.fdc3.proxy.steps", + "io.github.robmoffat.steps" +}) +public class TestSpringConfig { + + /** + * Create CustomWorld as a scenario-scoped bean. + */ + @Bean + @ScenarioScope(proxyMode = ScopedProxyMode.NO) + public CustomWorld customWorld() { + return new CustomWorld(); + } + + /** + * Provide CustomWorld as the implementation for PropsWorld. + * This ensures GenericSteps (which depends on PropsWorld) gets + * the same instance as AgentSteps (which depends on CustomWorld). + */ + @Bean + @Primary + @ScenarioScope(proxyMode = ScopedProxyMode.NO) + public PropsWorld propsWorld(CustomWorld customWorld) { + return customWorld; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/Fdc3SchemaMatchers.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/Fdc3SchemaMatchers.java new file mode 100644 index 00000000..2054e5f5 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/Fdc3SchemaMatchers.java @@ -0,0 +1,114 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.schema; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.JXPathNotFoundException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.ValidationMessage; + +import io.github.robmoffat.support.MatchingUtils; +import io.github.robmoffat.support.RowFieldMatcher; +import io.github.robmoffat.world.PropsWorld; + +/** + * Registers the {@code matches_type} table column matcher for FDC3 DACP messages. + * Java equivalent of {@code @finos/fdc3-schema/test/fdc3SchemaMatchers.ts}. + */ +public final class Fdc3SchemaMatchers { + + private static final String MATCHES_TYPE_SUFFIX = "matches_type"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final RowFieldMatcher MATCHES_TYPE_MATCHER = new RowFieldMatcher() { + @Override + public boolean matchesField(String field) { + return field.endsWith(MATCHES_TYPE_SUFFIX); + } + + @Override + @SuppressWarnings("unchecked") + public boolean matchField(PropsWorld world, String field, String schemaId, Object rowData) { + String path = MatchingUtils.pathForFieldSuffix(field, MATCHES_TYPE_SUFFIX); + if (path == null) { + return false; + } + + Object value = valueAtPath(rowData, path); + + Map schemas = (Map) world.get("schemas"); + if (schemas == null) { + world.log("Schemas not loaded — call Given schemas loaded first"); + return false; + } + + JsonSchema schema = schemas.get(schemaId); + if (schema == null) { + throw new IllegalStateException("No schema found for " + schemaId); + } + + try { + JsonNode jsonValue = OBJECT_MAPPER.valueToTree(value); + Set errors = schema.validate(jsonValue); + if (errors.isEmpty()) { + return true; + } + String messages = errors.stream() + .map(ValidationMessage::getMessage) + .collect(Collectors.joining("; ")); + world.log("Schema validation failed for " + schemaId + ": " + messages); + return false; + } catch (Exception e) { + world.log("Schema validation error for " + schemaId + ": " + e.getMessage()); + return false; + } + } + }; + + static { + MatchingUtils.registerFieldMatcher(MATCHES_TYPE_MATCHER); + } + + private Fdc3SchemaMatchers() { + } + + public static void registerFdc3SchemaMatchers() { + // Matcher registered in static initializer (same pattern as TypeScript side-effect import). + } + + private static Object valueAtPath(Object data, String path) { + if (path.isEmpty()) { + return data; + } + try { + JXPathContext context = JXPathContext.newContext(data); + context.setLenient(true); + String xpathName = "/" + path.replaceAll("\\.", "/"); + xpathName = xpathName.replaceAll("(/[^/]+)/length$", "count($1)"); + return context.getValue(xpathName); + } catch (JXPathNotFoundException e) { + return null; + } + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/LoadSchemas.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/LoadSchemas.java new file mode 100644 index 00000000..d88ae13d --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/LoadSchemas.java @@ -0,0 +1,205 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.schema; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SpecVersion; + +import io.github.robmoffat.world.PropsWorld; + +/** + * Loads FDC3 JSON schemas from local files into the test world. + * Java equivalent of {@code @finos/fdc3-schema/test/loadSchemas.ts} (Ajv {@code addSchema} pool). + */ +public final class LoadSchemas { + + private static final String SCHEMA_BASE = "https://fdc3.finos.org/schemas/next/api/"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String[] API_SCHEMA_LOAD_ORDER = { + "api.schema.json", + "common.schema.json", + "appRequest.schema.json", + "agentRequest.schema.json", + "agentResponse.schema.json", + "appResponse.schema.json", + }; + + private static volatile JsonSchemaFactory schemaFactory; + /** Compiled validators keyed by short schema id (e.g. {@code broadcastRequest}). */ + private static volatile Map schemaValidators; + + private LoadSchemas() { + } + + public static void loadSchemasIntoWorld(PropsWorld world) throws IOException { + if (world.get("schemas") != null) { + return; + } + world.set("schemas", loadSchemaValidators()); + world.set("jsonSchemaFactory", schemaFactory); + } + + public static Map loadSchemaValidators() throws IOException { + Map cached = schemaValidators; + if (cached != null) { + return cached; + } + synchronized (LoadSchemas.class) { + if (schemaValidators != null) { + return schemaValidators; + } + Path apiDir = resolveApiSchemaDirectory(); + if (apiDir == null || !Files.isDirectory(apiDir)) { + throw new IllegalStateException( + "Schema directory not found. Build fdc3-schema first (target/schema-work/api) or use FDC3 monorepo schemas at " + + "../../../FDC3/packages/fdc3-schema/schemas/api"); + } + + Map schemasByIri = loadSchemaDocuments(apiDir, resolveContextSchemaFile()); + + JsonSchemaFactory factory = JsonSchemaFactory.builder( + JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) + .schemaLoaders(loaders -> loaders.schemas(schemasByIri)) + .build(); + + Map validators = new HashMap<>(); + for (Path file : orderSchemaFiles(apiDir)) { + JsonNode node = MAPPER.readTree(file.toFile()); + String shortId = schemaIdFromFile(node, file.getFileName().toString()); + if (node.has("$id")) { + validators.put(shortId, factory.getSchema(SchemaLocation.of(node.get("$id").asText()))); + } + } + + schemaFactory = factory; + schemaValidators = Collections.unmodifiableMap(validators); + return schemaValidators; + } + } + + public static String schemaUri(String schemaId) { + return SCHEMA_BASE + schemaId + ".schema.json"; + } + + private static Map loadSchemaDocuments(Path apiDir, Path contextSchemaFile) throws IOException { + Map schemasByIri = new HashMap<>(); + + for (Path file : orderSchemaFiles(apiDir)) { + String content = Files.readString(file); + JsonNode node = MAPPER.readTree(content); + String filename = file.getFileName().toString(); + schemasByIri.put(filename, content); + if (node.has("$id")) { + schemasByIri.put(node.get("$id").asText(), content); + } + schemasByIri.put(file.toUri().toString(), content); + } + + if (contextSchemaFile != null && Files.exists(contextSchemaFile)) { + String content = Files.readString(contextSchemaFile); + JsonNode node = MAPPER.readTree(content); + schemasByIri.put("../context/context.schema.json", content); + schemasByIri.put(contextSchemaFile.toUri().toString(), content); + if (node.has("$id")) { + schemasByIri.put(node.get("$id").asText(), content); + } + } + + return schemasByIri; + } + + private static List orderSchemaFiles(Path apiDir) throws IOException { + List ordered = new java.util.ArrayList<>(); + Set seen = new LinkedHashSet<>(); + + for (String name : API_SCHEMA_LOAD_ORDER) { + Path p = apiDir.resolve(name); + if (Files.exists(p)) { + ordered.add(p); + seen.add(name); + } + } + + try (Stream files = Files.list(apiDir)) { + ordered.addAll(files.filter(p -> p.toString().endsWith(".json")) + .filter(p -> !seen.contains(p.getFileName().toString())) + .sorted(Comparator.comparing(p -> p.getFileName().toString())) + .collect(Collectors.toList())); + } + return ordered; + } + + private static Path resolveApiSchemaDirectory() { + Path[] candidates = { + Paths.get("../fdc3-schema/target/schema-work/api"), + Paths.get("../../../FDC3/packages/fdc3-schema/schemas/api"), + Paths.get("../fdc3-schema/target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas/api"), + Paths.get("../fdc3-schema/schemas/api"), + }; + for (Path candidate : candidates) { + if (Files.isDirectory(candidate)) { + return candidate.normalize(); + } + } + return null; + } + + private static Path resolveContextSchemaFile() { + Path[] candidates = { + Paths.get("../fdc3-schema/target/schema-work/context/context.schema.json"), + Paths.get("../../../FDC3/packages/fdc3-context/schemas/context/context.schema.json"), + Paths.get("../fdc3-context/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json"), + }; + for (Path candidate : candidates) { + if (Files.isRegularFile(candidate)) { + return candidate.normalize(); + } + } + return null; + } + + private static String schemaIdFromFile(JsonNode schemaJson, String filename) { + if (schemaJson.has("$id")) { + String id = schemaJson.get("$id").asText(); + if (id.startsWith(SCHEMA_BASE)) { + return id.substring(SCHEMA_BASE.length()).replace(".schema.json", ""); + } + return id; + } + return filename.replace(".schema.json", "").replace(".json", ""); + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java new file mode 100644 index 00000000..c4e498ed --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java @@ -0,0 +1,99 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.ui.Connectable; +import org.finos.fdc3.proxy.DesktopAgentProxy; +import org.finos.fdc3.proxy.apps.DefaultAppSupport; +import org.finos.fdc3.proxy.channels.DefaultChannelSupport; +import org.finos.fdc3.proxy.heartbeat.DefaultHeartbeatSupport; +import org.finos.fdc3.proxy.intents.DefaultIntentSupport; +import org.finos.fdc3.proxy.support.SimpleChannelSelector; +import org.finos.fdc3.proxy.support.SimpleIntentResolver; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.When; + +/** + * Agent step definitions for agent-proxy tests. + */ +public class AgentSteps { + + private final CustomWorld world; + + public AgentSteps(CustomWorld world) { + this.world = world; + } + + @Given("A Desktop Agent in {string}") + public void aDesktopAgentIn(String field) throws Exception { + createDesktopAgent(field, null); + } + + @Given("A Desktop Agent in {string} that puts apps on channel {string}") + public void aDesktopAgentInThatPutsAppsOnChannel(String field, String channelId) throws Exception { + createDesktopAgent(field, channelId); + } + + private void createDesktopAgent(String field, String initialChannelId) throws Exception { + if (!world.hasMessaging()) { + @SuppressWarnings("unchecked") + Map> channelState = (Map>) world.get(ChannelSteps.CHANNEL_STATE); + world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>(), initialChannelId)); + } + + TestMessaging messaging = world.getMessaging(); + + // Using short timeouts to avoid extending tests unnecessarily + DefaultChannelSupport cs = new DefaultChannelSupport(messaging, new SimpleChannelSelector(world), 1500); + DefaultHeartbeatSupport hs = new DefaultHeartbeatSupport(messaging, 30000); + DefaultIntentSupport is = new DefaultIntentSupport(messaging, new SimpleIntentResolver(world), 1500, 3000); + DefaultAppSupport as = new DefaultAppSupport(messaging, 1500, 3000); + + List connectables = new ArrayList<>(); + connectables.add(hs); + connectables.add(cs); + + DesktopAgentProxy da = new DesktopAgentProxy(hs, cs, is, as, connectables); + da.connect().toCompletableFuture().get(); + + world.set(field, da); + world.set("result", null); + } + + @When("messaging receives a heartbeat event") + public void messagingReceivesHeartbeatEvent() { + Map message = new HashMap<>(); + message.put("type", "heartbeatEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + + Map payload = new HashMap<>(); + payload.put("timestamp", java.time.Instant.now().toString()); + message.put("payload", payload); + + world.getMessaging().receive(message, null); + } + +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java new file mode 100644 index 00000000..dfcf3d26 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java @@ -0,0 +1,100 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.ui.Connectable; +import org.finos.fdc3.proxy.DesktopAgentProxy; +import org.finos.fdc3.proxy.apps.DefaultAppSupport; +import org.finos.fdc3.proxy.channels.DefaultChannelSupport; +import org.finos.fdc3.proxy.heartbeat.DefaultHeartbeatSupport; +import org.finos.fdc3.proxy.intents.DefaultIntentSupport; +import org.finos.fdc3.proxy.support.SimpleChannelSelector; +import org.finos.fdc3.proxy.support.SimpleIntentResolver; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.When; + +/** + * Cucumber step definitions for channel selector tests. + */ +public class ChannelSelectorSteps { + + private final CustomWorld world; + + public ChannelSelectorSteps(CustomWorld world) { + this.world = world; + } + + @Given("A Channel Selector in {string} and a Desktop Agent in {string}") + public void aChannelSelectorAndDesktopAgent(String selectorField, String daField) throws Exception { + if (!world.hasMessaging()) { + @SuppressWarnings("unchecked") + Map> channelState = (Map>) world.get(ChannelSteps.CHANNEL_STATE); + world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>())); + } + + TestMessaging messaging = world.getMessaging(); + SimpleChannelSelector ts = new SimpleChannelSelector(world); + world.set(selectorField, ts); + + // Create the DesktopAgentProxy with the test channel selector + DefaultChannelSupport cs = new DefaultChannelSupport(messaging, ts, 1500); + DefaultHeartbeatSupport hs = new DefaultHeartbeatSupport(messaging, 30000); + DefaultIntentSupport is = new DefaultIntentSupport(messaging, new SimpleIntentResolver(world), 1500, 3000); + DefaultAppSupport as = new DefaultAppSupport(messaging, 1500, 3000); + + List connectables = new ArrayList<>(); + connectables.add(hs); + + DesktopAgentProxy da = new DesktopAgentProxy(hs, cs, is, as, connectables); + da.connect().toCompletableFuture().get(); + + world.set(daField, da); + world.set("result", null); + + // populate the channel selector + List userChannels = cs.getUserChannels().toCompletableFuture().get(); + ts.updateChannel(null, userChannels); + } + + @When("The first channel is selected via the channel selector in {string}") + public void theFirstChannelIsSelected(String selectorField) { + SimpleChannelSelector selector = (SimpleChannelSelector) world.get(selectorField); + selector.selectFirstChannel(); + } + + @When("The second channel is selected via the channel selector in {string}") + public void theSecondChannelIsSelected(String selectorField) { + SimpleChannelSelector selector = (SimpleChannelSelector) world.get(selectorField); + selector.selectSecondChannel(); + } + + @When("The channel is deselected via the channel selector in {string}") + public void theChannelIsDeselected(String selectorField) { + SimpleChannelSelector selector = (SimpleChannelSelector) world.get(selectorField); + selector.deselectChannel(); + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java new file mode 100644 index 00000000..f42048f0 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java @@ -0,0 +1,435 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import static io.github.robmoffat.support.MatchingUtils.handleResolve; +import static io.github.robmoffat.support.MatchingUtils.matchData; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.DetachedSignature; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.proxy.support.ContextMap; +import org.finos.fdc3.proxy.world.CustomWorld; + + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +/** + * Cucumber step definitions for channel-related tests. + */ +public class ChannelSteps { + + public static final String CHANNEL_STATE = "CHANNEL_STATE"; + + private final CustomWorld world; + + public ChannelSteps(CustomWorld world) { + this.world = world; + } + + @Given("{string} is a {string} context") + public void isAContext(String field, String type) { + world.set(field, ContextMap.get(type)); + } + + @Given("{string} is a BroadcastEvent message on channel {string} with context {string}") + public void isABroadcastEventMessage(String field, String channel, String contextType) { + ContextMetadata metadata = defaultBroadcastMetadata(); + metadata.setSignature(new DetachedSignature()); + + Map message = new HashMap<>(); + message.put("type", "broadcastEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + Map payload = new HashMap<>(); + payload.put("channelId", handleResolve(channel, world)); + payload.put("context", ContextMap.get(contextType)); + payload.put("metadata", metadata); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a BroadcastEvent message on channel {string} with context {string} and metadata") + public void isABroadcastEventMessageWithMetadata(String field, String channel, String contextType) { + Map metadata = new HashMap<>(); + metadata.put("timestamp", Instant.now().toString()); + metadata.put("source", world.getMessaging().getAppIdentifier()); + metadata.put("traceId", world.getMessaging().createUUID()); + Map signature = new HashMap<>(); + signature.put("protected", "test-sig (protected part)"); + signature.put("signature", "test-sig (signature part)"); + metadata.put("signature", signature); + metadata.put("custom", Map.of("region", "EMEA")); + + Map message = new HashMap<>(); + message.put("type", "broadcastEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + Map payload = new HashMap<>(); + payload.put("channelId", handleResolve(channel, world)); + payload.put("context", ContextMap.get(contextType)); + payload.put("metadata", metadata); + message.put("payload", payload); + + world.set(field, message); + } + + private ContextMetadata defaultBroadcastMetadata() { + ContextMetadata metadata = ContextMetadata.appProvidable(); + metadata.setTimestamp(Instant.now()); + metadata.setSource(world.getMessaging().getAppIdentifier()); + metadata.setTraceId(world.getMessaging().createUUID()); + return metadata; + } + + @Given("{string} is a {string} message on channel {string}") + public void isAMessageOnChannel(String field, String type, String channel) { + Map message = new HashMap<>(); + message.put("type", type); + message.put("meta", world.getMessaging().createEventMeta()); + + Map payload = new HashMap<>(); + payload.put("privateChannelId", handleResolve(channel, world)); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a {string} message on channel {string} with listenerType as {string}") + public void isAMessageOnChannelWithListenerType(String field, String type, String channel, String listenerType) { + Map message = new HashMap<>(); + message.put("type", type); + message.put("meta", world.getMessaging().createMeta()); + + Map payload = new HashMap<>(); + payload.put("channelId", handleResolve(channel, world)); + payload.put("listenerType", listenerType); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a channelChangedEvent message on channel {string}") + public void isAChannelChangedEventMessage(String field, String channel) { + Map message = new HashMap<>(); + message.put("type", "channelChangedEvent"); + + Map meta = new HashMap<>(); + meta.put("eventUuid", world.getMessaging().createUUID()); + meta.put("timestamp", java.time.Instant.now().toString()); + message.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("newChannelId", handleResolve(channel, world)); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a channelChangedEvent message with currentChannelId {string}") + public void isAChannelChangedEventMessageWithCurrentChannelId(String field, String channelId) { + Map message = new HashMap<>(); + message.put("type", "channelChangedEvent"); + + Map meta = new HashMap<>(); + meta.put("eventUuid", world.getMessaging().createUUID()); + meta.put("timestamp", java.time.Instant.now().toString()); + message.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("currentChannelId", handleResolve(channelId, world)); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a channelChangedEvent message with currentChannelId {string} and newChannelId {string}") + public void isAChannelChangedEventMessageWithCurrentAndNewChannelId( + String field, String currentChannelId, String newChannelId) { + Map message = new HashMap<>(); + message.put("type", "channelChangedEvent"); + + Map meta = new HashMap<>(); + meta.put("eventUuid", world.getMessaging().createUUID()); + meta.put("timestamp", java.time.Instant.now().toString()); + message.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("currentChannelId", handleResolve(currentChannelId, world)); + payload.put("newChannelId", handleResolve(newChannelId, world)); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a PrivateChannelOnUnsubscribeEvent message on channel {string} with contextType as {string}") + public void isAPrivateChannelOnUnsubscribeEvent(String field, String channel, String contextType) { + Map message = new HashMap<>(); + message.put("type", "privateChannelOnUnsubscribeEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + + Map payload = new HashMap<>(); + payload.put("privateChannelId", handleResolve(channel, world)); + payload.put("contextType", contextType); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a PrivateChannelOnAddContextListenerEvent message on channel {string} with contextType as {string}") + public void isAPrivateChannelOnAddContextListenerEvent(String field, String channel, String contextType) { + Map message = new HashMap<>(); + message.put("type", "privateChannelOnAddContextListenerEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + + Map payload = new HashMap<>(); + payload.put("privateChannelId", handleResolve(channel, world)); + payload.put("contextType", contextType); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} is a PrivateChannelOnDisconnectEvent message on channel {string}") + public void isAPrivateChannelOnDisconnectEvent(String field, String channel) { + Map message = new HashMap<>(); + message.put("type", "privateChannelOnDisconnectEvent"); + message.put("meta", world.getMessaging().createEventMeta()); + + Map payload = new HashMap<>(); + payload.put("privateChannelId", handleResolve(channel, world)); + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} pipes types to {string}") + public void pipesTypesTo(String typeHandlerName, String field) { + List types = new ArrayList<>(); + world.set(field, types); + + class MyHandler implements ContextHandler, EventHandler { + + @Override + public void handleContext(Context context, ContextMetadata metadata) { + types.add(context.getType()); + + } + + @Override + public void handleEvent(FDC3Event event) { + @SuppressWarnings("unchecked") + Map details = (Map) event.getDetails(); + types.add((String) details.get("contextType")); + } + }; + + MyHandler ch = new MyHandler(); + + world.set(typeHandlerName, ch); + } + + @Given("{string} pipes events to {string}") + public void pipesEventsTo(String typeHandlerName, String field) { + List events = new ArrayList<>(); + world.set(field, events); + + EventHandler eh = new EventHandler() { + + @Override + public void handleEvent(FDC3Event event) { + events.add(event.getDetails()); + } + }; + + world.set(typeHandlerName, eh); + } + + @Given("{string} pipes context to {string}") + public void pipesContextTo(String contextHandlerName, String field) { + List contexts = new ArrayList<>(); + world.set(field, contexts); + + ContextHandler ch = new ContextHandler() { + + @Override + public void handleContext(Context context, ContextMetadata metadata) { + contexts.add(context); + + } + }; + + world.set(contextHandlerName, ch); + } + + @Given("{string} pipes context and metadata to {string} and {string}") + public void pipesContextAndMetadataTo(String contextHandlerName, String contextsField, String metadatasField) { + List contexts = new ArrayList<>(); + List metadatas = new ArrayList<>(); + world.set(contextsField, contexts); + world.set(metadatasField, metadatas); + + ContextHandler ch = new ContextHandler() { + @Override + public void handleContext(Context context, ContextMetadata metadata) { + contexts.add(context); + metadatas.add(metadata); + } + }; + + world.set(contextHandlerName, ch); + } + + @When("messaging receives {string}") + public void messagingReceives(String field) { + @SuppressWarnings("unchecked") + Map message = (Map) handleResolve(field, world); + System.out.println("Sending: " + message); + world.getMessaging().receive(message, System.out::println); + } + + @Then("messaging will have posts") + public void messagingWillHavePosts(DataTable dt) { + int matching = dt.height() - 1; // exclude header + List> toUse = world.getMessaging().getAllPosts(); + if (toUse.size() > matching) { + toUse = toUse.subList(toUse.size() - matching, toUse.size()); + } + matchData(world, toUse, dt); + } + + @Given("channel {string} has context {string}") + public void channelHasContext(String channel, String context) { + Context ctxObject = (Context) handleResolve(context, world); + @SuppressWarnings("unchecked") + Map> state = (Map>) world.get(CHANNEL_STATE); + if (state == null) { + state = new HashMap<>(); + world.set(CHANNEL_STATE, state); + } + + List cs = state.computeIfAbsent(channel, k -> new ArrayList<>()); + cs.add(ctxObject); + } + + @Given("User Channels one, two and three") + public void userChannelsOneTwoAndThree() { + Map> state = new HashMap<>(); + state.put("one", new ArrayList<>()); + state.put("two", new ArrayList<>()); + state.put("three", new ArrayList<>()); + world.set(CHANNEL_STATE, state); + } + + @When("I destructure methods {string}, {string} from {string}") + public void iDestructureMethods(String method1, String method2, String objectField) { + Object object = handleResolve(objectField, world); + world.set("destructured_" + method1, extractDestructuredMethod(object, method1)); + world.set("destructured_" + method2, extractDestructuredMethod(object, method2)); + } + + @When("I destructure method {string} from {string}") + public void iDestructureMethod(String methodName, String objectField) { + Object object = handleResolve(objectField, world); + world.set("destructured_" + methodName, extractDestructuredMethod(object, methodName)); + } + + private static DestructuredMethod extractDestructuredMethod(Object object, String methodName) { + return new DestructuredMethod(object, methodName); + } + + @When("I call destructured {string}") + public void iCallDestructured(String methodName) { + invokeDestructured(methodName); + } + + @When("I call destructured {string} using argument {string}") + public void iCallDestructuredUsingArgument(String methodName, String param) { + invokeDestructured(methodName, handleResolve(param, world)); + } + + @When("I call destructured {string} using arguments {string} and {string}") + public void iCallDestructuredUsingTwoArguments(String methodName, String param1, String param2) { + invokeDestructured(methodName, handleResolve(param1, world), handleResolve(param2, world)); + } + + @When("I call destructured {string} using arguments {string} and {string} and {string}") + public void iCallDestructuredUsingThreeArguments(String methodName, String param1, String param2, String param3) { + invokeDestructured( + methodName, + handleResolve(param1, world), + handleResolve(param2, world), + handleResolve(param3, world)); + } + + private void invokeDestructured(String methodName, Object... args) { + try { + DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); + if (dm == null) { + throw new IllegalStateException("No destructured method: " + methodName); + } + world.set("result", dm.invoke(args)); + } catch (Exception e) { + world.set("result", e); + } + } + + /** + * Helper class to hold a destructured method reference. + */ + public static class DestructuredMethod { + private final Object target; + private final String methodName; + + public DestructuredMethod(Object target, String methodName) { + this.target = target; + this.methodName = methodName; + } + + public Object invoke(Object... args) throws Exception { + java.lang.reflect.Method method = io.github.robmoffat.steps.GenericSteps.findMethod(target.getClass(), methodName, args); + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + + method.setAccessible(true); + Object result = method.invoke(target, args); + + // Handle CompletionStage/CompletableFuture + if (result instanceof java.util.concurrent.CompletionStage) { + result = ((java.util.concurrent.CompletionStage) result).toCompletableFuture().get(); + } + if (result instanceof java.util.Optional) { + return ((java.util.Optional) result).orElse(null); + } + return result; + } + + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java new file mode 100644 index 00000000..35fa18ad --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java @@ -0,0 +1,397 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.DetachedSignature; +import org.finos.fdc3.proxy.support.ParseAntiReplayClaims; +import org.finos.fdc3.api.metadata.DisplayMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.channels.DefaultChannel; +import org.finos.fdc3.proxy.channels.DefaultPrivateChannel; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.api.channel.Channel; + +import io.cucumber.java.en.Given; + +import static io.github.robmoffat.support.MatchingUtils.handleResolve; + +/** + * Cucumber step definitions for intent-related tests. + */ +public class IntentSteps { + + private final CustomWorld world; + + public IntentSteps(CustomWorld world) { + this.world = world; + } + + @Given("app {string}") + public void app(String appStr) { + String[] parts = appStr.split("/"); + String appId = parts[0]; + String instanceId = parts.length > 1 ? parts[1] : null; + + AppIdentifier app = createAppIdentifier(appId, instanceId); + world.getMessaging().addAppIntentDetail(createIntentDetail(app, null, null, null)); + if (instanceId != null) { + world.set(instanceId, app); + } + } + + @Given("app {string} resolves intent {string}") + public void appResolvesIntent(String appStr, String intent) { + String[] parts = appStr.split("/"); + String appId = parts[0]; + String instanceId = parts.length > 1 ? parts[1] : null; + + AppIdentifier app = createAppIdentifier(appId, instanceId); + world.getMessaging().addAppIntentDetail(createIntentDetail(app, intent, null, null)); + if (instanceId != null) { + world.set(instanceId, app); + } + world.set(appId, createAppIdentifier(appId, null)); + } + + @Given("app {string} resolves intent {string} with result type {string}") + public void appResolvesIntentWithResultType(String appStr, String intent, String resultType) { + String[] parts = appStr.split("/"); + String appId = parts[0]; + String instanceId = parts.length > 1 ? parts[1] : null; + + AppIdentifier app = createAppIdentifier(appId, instanceId); + world.getMessaging().addAppIntentDetail(createIntentDetail(app, intent, null, resultType)); + if (instanceId != null) { + world.set(instanceId, app); + } + world.set(appId, createAppIdentifier(appId, null)); + } + + @Given("app {string} resolves intent {string} with context {string}") + public void appResolvesIntentWithContext(String appStr, String intent, String context) { + String[] parts = appStr.split("/"); + String appId = parts[0]; + String instanceId = parts.length > 1 ? parts[1] : null; + + AppIdentifier app = createAppIdentifier(appId, instanceId); + world.getMessaging().addAppIntentDetail(createIntentDetail(app, intent, context, null)); + if (instanceId != null) { + world.set(instanceId, app); + } + world.set(appId, createAppIdentifier(appId, null)); + } + + @Given("app {string} resolves intent {string} with context {string} and result type {string}") + public void appResolvesIntentWithContextAndResultType(String appStr, String intent, String context, String resultType) { + String[] parts = appStr.split("/"); + String appId = parts[0]; + String instanceId = parts.length > 1 ? parts[1] : null; + + AppIdentifier app = createAppIdentifier(appId, instanceId); + world.getMessaging().addAppIntentDetail(createIntentDetail(app, intent, context, resultType)); + if (instanceId != null) { + world.set(instanceId, app); + } + } + + @Given("Raise Intent returns a context of {string}") + public void raiseIntentReturnsContext(String result) { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setContext((Context) handleResolve(result, world)); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns a context of {string} with traceId {string} and signature {string}") + public void raiseIntentReturnsContextWithMetadata(String result, String traceId, String signature) { + raiseIntentReturnsContextWithMetadataAndAntiReplay(result, traceId, signature, null); + } + + @Given("Raise Intent returns a context of {string} with traceId {string} and signature {string} and antiReplay claims {string}") + public void raiseIntentReturnsContextWithMetadataAndAntiReplay( + String result, String traceId, String signature, String antiReplayClaims) { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setContext((Context) handleResolve(result, world)); + intentResult.setResultMetadata(metadataWithTraceSignatureAndAntiReplay(traceId, signature, antiReplayClaims)); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("{string} is metadata with traceId {string} and signature {string}") + public void isMetadataWithTraceId(String field, String traceId, String signature) { + world.set(field, metadataWithTraceSignatureAndAntiReplay(traceId, signature, null)); + } + + @Given("{string} is metadata with traceId {string} and signature {string} and antiReplay claims {string}") + public void isMetadataWithTraceIdAndAntiReplay(String field, String traceId, String signature, String antiReplayClaims) { + world.set(field, metadataWithTraceSignatureAndAntiReplay(traceId, signature, antiReplayClaims)); + } + + private ContextMetadata metadataWithTraceSignatureAndAntiReplay( + String traceId, String signature, String antiReplayClaims) { + ContextMetadata metadata = ContextMetadata.appProvidable(); + metadata.setTraceId(traceId); + metadata.setSignature(testDetachedSignature(signature)); + if (antiReplayClaims != null) { + metadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); + } + Map custom = new HashMap<>(); + custom.put("priority", "high"); + metadata.setCustom(custom); + return metadata; + } + + @Given("Raise Intent will throw a {string} error") + public void raiseIntentWillThrowError(String error) { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setError(error); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns no result") + public void raiseIntentReturnsNoResult() { + world.getMessaging().setIntentResult(new TestMessaging.PossibleIntentResult()); + } + + @Given("Raise Intent times out") + public void raiseIntentTimesOut() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setTimeout(true); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns an app channel") + public void raiseIntentReturnsAppChannel() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setChannel(new DefaultChannel( + world.getMessaging(), + world.getMessaging().getTimeoutMs(), + "result-channel", + org.finos.fdc3.api.channel.Channel.Type.App, + null + )); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns a user channel") + public void raiseIntentReturnsUserChannel() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setChannel(new DefaultChannel( + world.getMessaging(), + world.getMessaging().getTimeoutMs(), + "result-channel", + org.finos.fdc3.api.channel.Channel.Type.User, + null + )); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns a private channel") + public void raiseIntentReturnsPrivateChannel() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setChannel(new DefaultPrivateChannel( + world.getMessaging(), + world.getMessaging().getTimeoutMs(), + "result-channel" + )); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("{string} is a intentEvent message with intent {string} and context {string}") + public void isAIntentEventMessage(String field, String intent, String context) { + Map message = new HashMap<>(); + message.put("type", "intentEvent"); + + Map meta = new HashMap<>(); + meta.put("eventUuid", world.getMessaging().createUUID()); + meta.put("timestamp", Instant.now().toString()); + message.put("meta", meta); + + Map payload = new HashMap<>(); + Map originatingApp = new HashMap<>(); + originatingApp.put("appId", "some-app-id"); + originatingApp.put("desktopAgent", "some-desktop-agent"); + payload.put("originatingApp", originatingApp); + payload.put("context", handleResolve(context, world)); + payload.put("intent", intent); + payload.put("raiseIntentRequestUuid", "request-id"); + + ContextMetadata metadata = ContextMetadata.appProvidable(); + metadata.setTimestamp(Instant.now()); + metadata.setSource(world.getMessaging().getAppIdentifier()); + metadata.setTraceId(world.getMessaging().createUUID()); + payload.put("metadata", metadata); + + message.put("payload", payload); + + world.set(field, message); + } + + @Given("{string} pipes intent to {string}") + public void pipesIntentTo(String intentHandlerName, String field) { + List> intents = new ArrayList<>(); + world.set(field, intents); + + IntentHandler ih = new IntentHandler() { + + @Override + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + Map item = new HashMap<>(); + item.put("context", context); + item.put("metadata", contextMetadata); + intents.add(item); + return CompletableFuture.completedFuture(Optional.empty()); + } + }; + + world.set(intentHandlerName, ih); + } + + @Given("{string} returns a context item") + public void returnsAContextItem(String intentHandlerName) { + IntentHandler ih = new IntentHandler() { + + @Override + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + Map id = new HashMap<>(); + id.put("in", "one"); + id.put("out", "two"); + return CompletableFuture.completedFuture(Optional.of(new Context("fdc3.returned-intent", null, id))); + } + }; + + world.set(intentHandlerName, ih); + } + + @Given("{string} returns a channel") + public void returnsAChannel(String intentHandlerName) { + IntentHandler ih = new IntentHandler() { + + @Override + public CompletionStage> handleIntent(Context context, + ContextMetadata contextMetadata) { + DisplayMetadata dm = new DisplayMetadata("Some Channel", "ochre","b;"); + Channel c = new Channel() { + + @Override + public String getId() { + return "some-channel-id"; + } + + @Override + public Type getType() { + return Type.Private; + } + + @Override + public DisplayMetadata getDisplayMetadata() { + return dm; + } + + @Override + public CompletionStage broadcast(Context context) { + return null; + } + + @Override + public CompletionStage> getCurrentContext() { + return null; + } + + @Override + public CompletionStage> getCurrentContext(String contextType) { + return null; + } + + @Override + public CompletionStage addContextListener(String contextType, ContextHandler handler) { + return null; + } + + @Override + public CompletionStage addContextListener(ContextHandler handler) { + return null; + } + + @Override + public CompletionStage> getCurrentContextWithMetadata( + String contextType) { + return null; + } + + @Override + public CompletionStage broadcast(Context context, AppProvidableContextMetadata metadata) { + return null; + } + + }; + return CompletableFuture.completedFuture(Optional.of(c)); + } + + }; + world.set(intentHandlerName, ih); + } + + @Given("{string} returns a void promise") + public void returnsAVoidPromise(String intentHandlerName) { + IntentHandler ih = new IntentHandler() { + + @Override + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + return CompletableFuture.completedFuture(Optional.ofNullable(null)); + } + }; + world.set(intentHandlerName, ih); + } + + private AppIdentifier createAppIdentifier(String appId, String instanceId) { + return new AppIdentifier(appId, instanceId, "some-desktop-agent"); + } + + private TestMessaging.IntentDetail createIntentDetail(AppIdentifier app, String intent, String context, String resultType) { + TestMessaging.IntentDetail detail = new TestMessaging.IntentDetail(); + detail.setApp(app); + detail.setIntent(intent); + detail.setContext(context); + detail.setResultType(resultType); + return detail; + } + + private static DetachedSignature testDetachedSignature(String base) { + DetachedSignature sig = new DetachedSignature(); + sig.setSignature(base + " (signature part)"); + sig.setProtectedHeader(base + " (protected part)"); + return sig; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/SchemaSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/SchemaSteps.java new file mode 100644 index 00000000..885ad288 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/SchemaSteps.java @@ -0,0 +1,48 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import java.io.IOException; + +import org.finos.fdc3.proxy.schema.Fdc3SchemaMatchers; +import org.finos.fdc3.proxy.schema.LoadSchemas; + +import io.cucumber.java.en.Given; +import io.github.robmoffat.world.PropsWorld; + +/** + * Cucumber glue for FDC3 schema loading and matchers. + * Equivalent to {@code setupSchemaSteps()} and {@code registerFdc3SchemaMatchers()} in + * {@code @finos/fdc3-schema/test}. + */ +public class SchemaSteps { + + static { + Fdc3SchemaMatchers.registerFdc3SchemaMatchers(); + } + + private final PropsWorld world; + + public SchemaSteps(PropsWorld world) { + this.world = world; + } + + @Given("schemas loaded") + public void schemasLoaded() throws IOException { + LoadSchemas.loadSchemasIntoWorld(world); + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java new file mode 100644 index 00000000..983a72e5 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java @@ -0,0 +1,86 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.steps; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.en.When; + +/** + * Cucumber step definitions for utility tests. + */ +public class UtilSteps { + + private final CustomWorld world; + + public UtilSteps(CustomWorld world) { + this.world = world; + } + + @When("I call throwIfUndefined it throws if a specified property is not defined") + public void throwIfUndefinedThrows() { + Exception thrown = null; + try { + Object value = null; + if (value == null) { + throw new IllegalArgumentException("OpenError.MalformedContext"); + } + } catch (Exception e) { + thrown = e; + } + + assertNotNull(thrown); + assertEquals("OpenError.MalformedContext", thrown.getMessage()); + } + + @When("I call throwIfUndefined it does NOT throw if a specified property IS defined") + public void throwIfUndefinedDoesNotThrow() { + Exception thrown = null; + try { + Object value = "some-value"; + if (value == null) { + throw new IllegalArgumentException("OpenError.MalformedContext"); + } + } catch (Exception e) { + thrown = e; + } + + assertNull(thrown); + } + + @When("All log functions are used with a message") + public void allLogFunctionsWithMessage() { + System.out.println("[DEBUG] Debug msg"); + System.out.println("[LOG] Log msg"); + System.out.println("[WARN] Warning msg"); + System.out.println("[ERROR] Error msg"); + } + + @When("All log functions are used with an error") + public void allLogFunctionsWithError() { + String testError = "Test error - This is expected on the console"; + System.out.println("[DEBUG] debug-level error: " + new Exception(testError)); + System.out.println("[LOG] log-level error: " + new Exception(testError)); + System.out.println("[WARN] warn-level error: " + new Exception(testError)); + System.out.println("[ERROR] error-level error: " + new Exception(testError)); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java new file mode 100644 index 00000000..a7529493 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java @@ -0,0 +1,67 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.support; + +import java.util.HashMap; +import java.util.Map; + +import org.finos.fdc3.api.context.Context; + +/** + * Pre-defined context objects for testing. + */ +public final class ContextMap { + + private static final Map CONTEXTS = new HashMap<>(); + + static { + // fdc3.instrument + Map instrumentId = new HashMap<>(); + instrumentId.put("ticker", "AAPL"); + Context instrument = new Context("fdc3.instrument", "Apple", instrumentId); + CONTEXTS.put("fdc3.instrument", instrument); + + // fdc3.country + Map countryId = new HashMap<>(); + countryId.put("COUNTRY_ISOALPHA2", "SE"); + countryId.put("COUNTRY_ISOALPHA3", "SWE"); + Context country = new Context("fdc3.country", "Sweden", countryId); + CONTEXTS.put("fdc3.country", country); + + // fdc3.unsupported + Context unsupported = new Context("fdc3.unsupported"); + unsupported.put("bogus", true); + CONTEXTS.put("fdc3.unsupported", unsupported); + + // fdc3.cancel-me + Context cancelMe = new Context("fdc3.cancel-me"); + CONTEXTS.put("fdc3.cancel-me", cancelMe); + } + + private ContextMap() { + // Utility class + } + + public static Context get(String type) { + return CONTEXTS.get(type); + } + + public static boolean contains(String type) { + return CONTEXTS.containsKey(type); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ParseAntiReplayClaims.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ParseAntiReplayClaims.java new file mode 100644 index 00000000..f7817f8d --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ParseAntiReplayClaims.java @@ -0,0 +1,36 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.support; + +import org.finos.fdc3.api.metadata.AntiReplayClaims; + +public final class ParseAntiReplayClaims { + + private ParseAntiReplayClaims() { + } + + public static AntiReplayClaims parse(String claims) { + String[] parts = claims.split("/"); + if (parts.length != 3) { + throw new IllegalArgumentException( + "antiReplay claims must be three slash-separated parts (iat/exp/jti), got: " + claims); + } + long iat = Long.parseLong(parts[0]); + long exp = Long.parseLong(parts[1]); + return new AntiReplayClaims(iat, exp, parts[2]); + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java new file mode 100644 index 00000000..2f550bd7 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java @@ -0,0 +1,111 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.ui.ChannelSelector; + +import io.github.robmoffat.world.PropsWorld; + +/** + * A simple channel selector for testing purposes. + *

+ * This selector stores channel state in the PropsWorld for verification. + *

+ * This is equivalent to the TypeScript SimpleChannelSelector class. + */ +public class SimpleChannelSelector implements ChannelSelector { + + public static final String CHANNEL_STATE = "CHANNEL_STATE"; + + private final PropsWorld world; + private Consumer channelChangeCallback; + private String channelId; + private List channels = new ArrayList<>(); + + public SimpleChannelSelector(PropsWorld world) { + this.world = world; + } + + @Override + public void updateChannel(String channelId, List availableChannels) { + this.channelId = channelId; + this.channels = new ArrayList<>(availableChannels); + world.set("channelId", channelId); + world.set("channels", availableChannels); + } + + @Override + public void setChannelChangeCallback(Consumer callback) { + this.channelChangeCallback = callback; + } + + /** + * Simulate a channel change (for testing). + * + * @param channelId the new channel ID, or null to leave channel + */ + public void selectChannel(String channelId) { + this.channelId = channelId; + if (channelChangeCallback != null) { + channelChangeCallback.accept(channelId); + } else { + throw new IllegalStateException("Channel selected before Channel Change callback was set!"); + } + } + + public void selectFirstChannel() { + if (!channels.isEmpty()) { + selectChannel(channels.get(0).getId()); + } + } + + public void selectSecondChannel() { + if (channels.size() > 1) { + selectChannel(channels.get(1).getId()); + } + } + + public void deselectChannel() { + selectChannel(null); + } + + public String getChannelId() { + return channelId; + } + + public List getChannels() { + return channels; + } + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java new file mode 100644 index 00000000..863c950d --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java @@ -0,0 +1,92 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.support; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.IntentMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.ui.IntentResolutionChoice; +import org.finos.fdc3.api.ui.IntentResolver; +import io.github.robmoffat.world.PropsWorld; + +/** + * A simple intent resolver for testing purposes. + *

+ * This resolver automatically selects the first intent/app in the list, + * unless the context type is "fdc3.cancel-me", in which case it returns null (cancelled). + *

+ * This is equivalent to the TypeScript SimpleIntentResolver class. + */ +public class SimpleIntentResolver implements IntentResolver { + + private final PropsWorld world; + + public SimpleIntentResolver(PropsWorld world) { + this.world = world; + } + + @Override + public CompletionStage chooseIntent(List appIntents, Context context) { + // Cancel if the context type is "fdc3.cancel-me" + if ("fdc3.cancel-me".equals(context.getType())) { + return CompletableFuture.completedFuture(null); + } + + // Select the first intent and first app + AppIntent firstIntent = appIntents.get(0); + IntentMetadata intent = firstIntent.getIntent(); + // getApps() returns AppMetadata[] so convert to list + List apps = Arrays.asList(firstIntent.getApps()); + AppMetadata firstApp = apps.get(0); + + // Create an AppIdentifier from the AppMetadata + AppIdentifier appIdentifier = new AppIdentifier( + firstApp.getAppId(), + firstApp.getInstanceId(), + firstApp.getDesktopAgent() + ); + + // Create the resolution choice + IntentResolutionChoice resolution = new IntentResolutionChoice( + intent.getName(), + appIdentifier + ); + + // Store for testing verification + world.set("intent-resolution", resolution); + + return CompletableFuture.completedFuture(resolution); + } + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java new file mode 100644 index 00000000..67448eb3 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java @@ -0,0 +1,325 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.support; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.proxy.messaging.AbstractMessaging; +import org.finos.fdc3.proxy.support.responses.AddEventListenerResponse; +import org.finos.fdc3.proxy.support.responses.AutomaticResponse; +import org.finos.fdc3.proxy.support.responses.ChannelStateResponse; +import org.finos.fdc3.proxy.support.responses.CreatePrivateChannelResponse; +import org.finos.fdc3.proxy.support.responses.DisconnectPrivateChannelResponse; +import org.finos.fdc3.proxy.support.responses.FindInstancesResponse; +import org.finos.fdc3.proxy.support.responses.FindIntentByContextResponse; +import org.finos.fdc3.proxy.support.responses.FindIntentResponse; +import org.finos.fdc3.proxy.support.responses.GetAppMetadataResponse; +import org.finos.fdc3.proxy.support.responses.GetInfoResponse; +import org.finos.fdc3.proxy.support.responses.GetOrCreateChannelResponse; +import org.finos.fdc3.proxy.support.responses.GetUserChannelsResponse; +import org.finos.fdc3.proxy.support.responses.IntentResultResponse; +import org.finos.fdc3.proxy.support.responses.OpenResponse; +import org.finos.fdc3.proxy.support.responses.RaiseIntentForContextResponse; +import org.finos.fdc3.proxy.support.responses.RaiseIntentResponse; +import org.finos.fdc3.proxy.support.responses.RegisterListenersResponse; +import org.finos.fdc3.proxy.support.responses.UnsubscribeListenersResponse; + +/** + * Test implementation of messaging for Cucumber tests. + * Simulates the message exchange between the Desktop Agent and apps. + */ +public class TestMessaging extends AbstractMessaging { + + private final List> allPosts = new ArrayList<>(); + private final Map listeners = new ConcurrentHashMap<>(); + private final List intentDetails = new ArrayList<>(); + private final Map> channelState; + private final List automaticResponses; + + private Channel currentChannel; + private PossibleIntentResult intentResult; + + public TestMessaging(Map> channelState) { + this(channelState, null); + } + + public TestMessaging(Map> channelState, String initialChannelId) { + super(new AppIdentifier("cucumber-app", "cucumber-instance", "testing-da")); + this.channelState = channelState != null ? channelState : new HashMap<>(); + + // Set up automatic responses for various message types + this.automaticResponses = new ArrayList<>(); + this.automaticResponses.add(new FindIntentResponse()); + this.automaticResponses.add(new FindIntentByContextResponse()); + this.automaticResponses.add(new RaiseIntentResponse()); + this.automaticResponses.add(new RaiseIntentForContextResponse()); + this.automaticResponses.add(new IntentResultResponse()); + this.automaticResponses.add(new GetAppMetadataResponse()); + this.automaticResponses.add(new GetInfoResponse()); + this.automaticResponses.add(new FindInstancesResponse()); + this.automaticResponses.add(new OpenResponse()); + this.automaticResponses.add(new GetOrCreateChannelResponse()); + this.automaticResponses.add(new ChannelStateResponse(this.channelState, initialChannelId)); + this.automaticResponses.add(new GetUserChannelsResponse()); + this.automaticResponses.add(new RegisterListenersResponse()); + this.automaticResponses.add(new UnsubscribeListenersResponse()); + this.automaticResponses.add(new CreatePrivateChannelResponse()); + this.automaticResponses.add(new DisconnectPrivateChannelResponse()); + } + + @Override + public String createUUID() { + return UUID.randomUUID().toString(); + } + + public int getTimeoutMs() { + return 1000; + } + + public synchronized List> getAllPosts() { + return allPosts; + } + + @Override + public synchronized CompletionStage post(Map message) { + System.out.println("Post: "+message.get("type")); + allPosts.add(message); + + String type = (String) message.get("type"); + if (!"WCP6Goodbye".equals(type)) { + for (AutomaticResponse ar : automaticResponses) { + if (ar.filter(type)) { + return ar.action(message, this); + } + } + } + + return CompletableFuture.completedFuture(null); + } + + @Override + public void register(RegisterableListener listener) { + if (listener.getId() == null) { + throw new IllegalArgumentException("Listener must have ID set"); + } + listeners.put(listener.getId(), listener); + } + + @Override + public void unregister(String id) { + listeners.remove(id); + } + + @Override + public CompletionStage disconnect() { + Map bye = new HashMap<>(); + bye.put("type", "WCP6Goodbye"); + Map meta = new HashMap<>(); + meta.put("timestamp", OffsetDateTime.now().toString()); + bye.put("meta", meta); + return post(bye); + } + + public void addAppIntentDetail(IntentDetail detail) { + intentDetails.add(detail); + } + + public List getIntentDetails() { + return intentDetails; + } + + /** + * Used in testing steps to create response metadata. + */ + public Map createResponseMeta() { + Map meta = new HashMap<>(); + meta.put("requestUuid", createUUID()); + meta.put("responseUuid", createUUID()); + meta.put("timestamp", Instant.now().toString()); + Map source = new HashMap<>(); + source.put("appId", getAppIdentifier().getAppId()); + if (getAppIdentifier().getInstanceId() != null) { + source.put("instanceId", getAppIdentifier().getInstanceId()); + } + meta.put("source", source); + return meta; + } + + /** + * Used in testing steps to create event metadata. + */ + public Map createEventMeta() { + Map meta = new HashMap<>(); + meta.put("requestUuid", createUUID()); + meta.put("eventUuid", createUUID()); + meta.put("timestamp", Instant.now().toString()); + Map source = new HashMap<>(); + source.put("appId", getAppIdentifier().getAppId()); + if (getAppIdentifier().getInstanceId() != null) { + source.put("instanceId", getAppIdentifier().getInstanceId()); + } + meta.put("source", source); + return meta; + } + + /** + * Simulates receiving a message from the Desktop Agent. + * Dispatches to all registered listeners that match the message. + */ + public void receive(Map message, Consumer log) { + listeners.forEach((id, listener) -> { + if (listener.filter(message)) { + if (log != null) { + log.accept("Processing in " + id); + } + listener.action(message); + } else { + if (log != null) { + log.accept("Ignoring in " + id); + } + } + }); + } + + public PossibleIntentResult getIntentResult() { + return intentResult; + } + + public void setIntentResult(PossibleIntentResult result) { + this.intentResult = result; + } + + public Channel getCurrentChannel() { + return currentChannel; + } + + public void setCurrentChannel(Channel channel) { + this.currentChannel = channel; + } + + public Map> getChannelState() { + return channelState; + } + + /** + * Represents details about an intent that can be resolved. + */ + public static class IntentDetail { + private AppIdentifier app; + private String intent; + private String context; + private String resultType; + + public AppIdentifier getApp() { + return app; + } + + public void setApp(AppIdentifier app) { + this.app = app; + } + + public String getIntent() { + return intent; + } + + public void setIntent(String intent) { + this.intent = intent; + } + + public String getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + public String getResultType() { + return resultType; + } + + public void setResultType(String resultType) { + this.resultType = resultType; + } + } + + /** + * Represents a possible result from raising an intent. + */ + public static class PossibleIntentResult { + private Context context; + private Channel channel; + private ContextMetadata resultMetadata; + private String error; + private boolean timeout; + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + + public Channel getChannel() { + return channel; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } + + public org.finos.fdc3.api.metadata.ContextMetadata getResultMetadata() { + return resultMetadata; + } + + public void setResultMetadata(org.finos.fdc3.api.metadata.ContextMetadata resultMetadata) { + this.resultMetadata = resultMetadata; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public boolean isTimeout() { + return timeout; + } + + public void setTimeout(boolean timeout) { + this.timeout = timeout; + } + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AddEventListenerResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AddEventListenerResponse.java new file mode 100644 index 00000000..eb597695 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AddEventListenerResponse.java @@ -0,0 +1,46 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to addEventListener requests. + */ +public class AddEventListenerResponse implements AutomaticResponse { + + private final AtomicInteger count = new AtomicInteger(0); + + @Override + public boolean filter(String messageType) { + return "addEventListenerRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + Map payload = new HashMap<>(); + payload.put("listenerUUID", "listener-" + count.getAndIncrement()); + + Map response = new HashMap<>(); + response.put("type", "addEventListenerResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AutomaticResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AutomaticResponse.java new file mode 100644 index 00000000..e865b33c --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AutomaticResponse.java @@ -0,0 +1,26 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import java.util.Map; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Interface for automatic responses to test messages. + */ +public interface AutomaticResponse { + + /** + * Returns true if this response should handle messages of the given type. + */ + boolean filter(String messageType); + + /** + * Processes the message and sends an appropriate response. + */ + CompletionStage action(Map message, TestMessaging messaging); +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java new file mode 100644 index 00000000..5b63ddc6 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java @@ -0,0 +1,259 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Handles channel-related requests: broadcast, join, leave, getCurrentChannel, + * addContextListener, contextListenerUnsubscribe, getCurrentContext. + * + * Equivalent to ChannelState.ts in the TypeScript implementation. + */ +public class ChannelStateResponse implements AutomaticResponse { + + private String channelId = null; + private final Map> listeners = new HashMap<>(); + private final Map> contextHistory; + + public ChannelStateResponse(Map> contextHistory) { + this(contextHistory, null); + } + + public ChannelStateResponse(Map> contextHistory, String initialChannelId) { + this.contextHistory = contextHistory != null ? contextHistory : new HashMap<>(); + this.channelId = initialChannelId; + } + + @Override + public boolean filter(String messageType) { + return "broadcastRequest".equals(messageType) || + "joinUserChannelRequest".equals(messageType) || + "leaveCurrentChannelRequest".equals(messageType) || + "getCurrentChannelRequest".equals(messageType) || + "addContextListenerRequest".equals(messageType) || + "contextListenerUnsubscribeRequest".equals(messageType) || + "getCurrentContextRequest".equals(messageType); + } + + @Override + public CompletionStage action(Map message, TestMessaging messaging) { + String type = (String) message.get("type"); + Map response = null; + + switch (type) { + case "broadcastRequest": + response = createBroadcastResponse(message); + break; + case "joinUserChannelRequest": + response = createJoinResponse(message); + break; + case "leaveCurrentChannelRequest": + response = createLeaveResponse(message); + break; + case "getCurrentChannelRequest": + response = createGetChannelResponse(message); + break; + case "addContextListenerRequest": + response = createAddListenerResponse(message); + break; + case "contextListenerUnsubscribeRequest": + response = createUnsubscribeResponse(message); + break; + case "getCurrentContextRequest": + response = createGetContextResponse(message); + break; + } + + if (response != null) { + scheduleReceive(messaging, response); + } + return CompletableFuture.completedFuture(null); + } + + @SuppressWarnings("unchecked") + private Map createBroadcastResponse(Map message) { + Map meta = (Map) message.get("meta"); + Map payload = (Map) message.get("payload"); + String channel = (String) payload.get("channelId"); + Object context = payload.get("context"); + + // Store context in history + contextHistory.computeIfAbsent(channel, k -> new ArrayList<>()); + if (context instanceof Context) { + contextHistory.get(channel).add(0, (Context) context); + } else if (context instanceof Map) { + contextHistory.get(channel).add(0, Context.fromMap((Map) context)); + } + + Map response = new HashMap<>(); + response.put("type", "broadcastResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + return response; + } + + @SuppressWarnings("unchecked") + private Map createJoinResponse(Map message) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + String requestedChannel = (String) msgPayload.get("channelId"); + + Map responsePayload = new HashMap<>(); + + if ("nonexistent".equals(requestedChannel)) { + responsePayload.put("error", "NoChannelFound"); + } else { + this.channelId = requestedChannel; + } + + Map response = new HashMap<>(); + response.put("type", "joinUserChannelResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", responsePayload); + return response; + } + + @SuppressWarnings("unchecked") + private Map createLeaveResponse(Map message) { + Map meta = (Map) message.get("meta"); + this.channelId = null; + + Map response = new HashMap<>(); + response.put("type", "leaveCurrentChannelResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + return response; + } + + @SuppressWarnings("unchecked") + private Map createGetChannelResponse(Map message) { + Map meta = (Map) message.get("meta"); + + Map responsePayload = new HashMap<>(); + if (this.channelId != null) { + Map channel = new HashMap<>(); + channel.put("id", this.channelId); + channel.put("type", "user"); + + Map displayMetadata = new HashMap<>(); + displayMetadata.put("name", "The " + this.channelId + " channel"); + displayMetadata.put("color", "red"); + displayMetadata.put("glyph", "triangle"); + channel.put("displayMetadata", displayMetadata); + + responsePayload.put("channel", channel); + } else { + responsePayload.put("channel", null); + } + + Map response = new HashMap<>(); + response.put("type", "getCurrentChannelResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", responsePayload); + return response; + } + + @SuppressWarnings("unchecked") + private Map createAddListenerResponse(Map message) { + Map meta = (Map) message.get("meta"); + String id = UUID.randomUUID().toString(); + + if (this.channelId != null) { + listeners.computeIfAbsent(this.channelId, k -> new ArrayList<>()); + listeners.get(this.channelId).add(id); + } + + Map responsePayload = new HashMap<>(); + responsePayload.put("listenerUUID", id); + + Map response = new HashMap<>(); + response.put("type", "addContextListenerResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", responsePayload); + return response; + } + + @SuppressWarnings("unchecked") + private Map createUnsubscribeResponse(Map message) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + String listenerId = (String) msgPayload.get("listenerUUID"); + + // Remove listener from all channels + for (List channelListeners : listeners.values()) { + channelListeners.remove(listenerId); + } + + Map response = new HashMap<>(); + response.put("type", "contextListenerUnsubscribeResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + return response; + } + + @SuppressWarnings("unchecked") + private Map createGetContextResponse(Map message) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + String channel = (String) msgPayload.get("channelId"); + String contextType = (String) msgPayload.get("contextType"); + + List contexts = contextHistory.get(channel); + Context foundContext = null; + + if (contexts != null && !contexts.isEmpty()) { + if (contextType != null) { + // Find matching context type + for (Context ctx : contexts) { + if (contextType.equals(ctx.getType())) { + foundContext = ctx; + break; + } + } + } else { + // Return first (most recent) context + foundContext = contexts.get(0); + } + } + + Map responsePayload = new HashMap<>(); + responsePayload.put("context", foundContext); + if (foundContext != null) { + Map metadata = new HashMap<>(); + Map source = new HashMap<>(); + source.put("appId", "test-app"); + source.put("instanceId", "test-instance"); + metadata.put("source", source); + metadata.put("timestamp", java.time.Instant.now().toString()); + metadata.put("traceId", "test-trace-id"); + Map signature = new HashMap<>(); + signature.put("protected", "test-signature (protected part)"); + signature.put("signature", "test-signature (signature part)"); + metadata.put("signature", signature); + Map custom = new HashMap<>(); + custom.put("key", "value"); + metadata.put("custom", custom); + responsePayload.put("metadata", metadata); + } + + Map response = new HashMap<>(); + response.put("type", "getCurrentContextResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", responsePayload); + return response; + } +} diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java new file mode 100644 index 00000000..2b122bfe --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java @@ -0,0 +1,50 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to createPrivateChannel requests. + */ +public class CreatePrivateChannelResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "createPrivateChannelRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + String channelId = "private-" + UUID.randomUUID().toString(); + + Map channel = new HashMap<>(); + channel.put("id", channelId); + channel.put("type", "private"); + + Map payload = new HashMap<>(); + payload.put("privateChannel", channel); + + Map response = new HashMap<>(); + response.put("type", "createPrivateChannelResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/DisconnectPrivateChannelResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/DisconnectPrivateChannelResponse.java new file mode 100644 index 00000000..d5bccc8b --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/DisconnectPrivateChannelResponse.java @@ -0,0 +1,40 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to privateChannelDisconnect requests. + */ +public class DisconnectPrivateChannelResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "privateChannelDisconnectRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + Map response = new HashMap<>(); + response.put("type", "privateChannelDisconnectResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindInstancesResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindInstancesResponse.java new file mode 100644 index 00000000..d723d85e --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindInstancesResponse.java @@ -0,0 +1,50 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to findInstances requests. + */ +public class FindInstancesResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "findInstancesRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + List> appIdentifiers = new ArrayList<>(); + appIdentifiers.add(Map.of("appId", "One", "instanceId", "1")); + appIdentifiers.add(Map.of("appId", "Two", "instanceId", "2")); + appIdentifiers.add(Map.of("appId", "Three", "instanceId", "3")); + + Map payload = new HashMap<>(); + payload.put("appIdentifiers", appIdentifiers); + + Map response = new HashMap<>(); + response.put("type", "findInstancesResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentByContextResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentByContextResponse.java new file mode 100644 index 00000000..6868d787 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentByContextResponse.java @@ -0,0 +1,93 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; + +/** + * Responds to findIntentsByContext requests. + */ +public class FindIntentByContextResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "findIntentsByContextRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + + Map context = (Map) msgPayload.get("context"); + String contextType = context != null ? (String) context.get("type") : null; + + // Find matching intent details by context type + List matching = new ArrayList<>(); + for (IntentDetail detail : messaging.getIntentDetails()) { + if (contextType == null || contextType.equals(detail.getContext())) { + matching.add(detail); + } + } + + // Get unique intent names + Set uniqueIntents = new LinkedHashSet<>(); + for (IntentDetail detail : matching) { + if (detail.getIntent() != null) { + uniqueIntents.add(detail.getIntent()); + } + } + + // Build appIntents + List> appIntents = new ArrayList<>(); + for (String intentName : uniqueIntents) { + Map intentInfo = new LinkedHashMap<>(); + intentInfo.put("name", intentName); + intentInfo.put("displayName", intentName); + + List> apps = new ArrayList<>(); + for (IntentDetail detail : matching) { + if (intentName.equals(detail.getIntent()) && detail.getApp() != null) { + Map app = new LinkedHashMap<>(); + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } + apps.add(app); + } + } + + Map appIntent = new HashMap<>(); + appIntent.put("intent", intentInfo); + appIntent.put("apps", apps); + appIntents.add(appIntent); + } + + Map payload = new HashMap<>(); + payload.put("appIntents", appIntents); + + Map response = new HashMap<>(); + response.put("type", "findIntentsByContextResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentResponse.java new file mode 100644 index 00000000..ec14fa20 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentResponse.java @@ -0,0 +1,118 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; + +/** + * Responds to findIntent requests. + */ +public class FindIntentResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "findIntentRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + + String intent = (String) msgPayload.get("intent"); + Map context = (Map) msgPayload.get("context"); + String contextType = context != null ? (String) context.get("type") : null; + String resultType = (String) msgPayload.get("resultType"); + + // Find matching intent details + List matching = new ArrayList<>(); + for (IntentDetail detail : messaging.getIntentDetails()) { + if (intentDetailMatches(detail, intent, contextType, resultType)) { + matching.add(detail); + } + } + + // Build app list + List> apps = new ArrayList<>(); + for (IntentDetail detail : matching) { + if (detail.getApp() != null && detail.getApp().getAppId() != null) { + Map app = new HashMap<>(); + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } + apps.add(app); + } + } + + Map intentInfo = new HashMap<>(); + intentInfo.put("name", intent); + intentInfo.put("displayName", intent); + + Map appIntent = new HashMap<>(); + appIntent.put("intent", intentInfo); + appIntent.put("apps", apps); + + Map payload = new HashMap<>(); + payload.put("appIntent", appIntent); + + Map response = new HashMap<>(); + response.put("type", "findIntentResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } + + private boolean intentDetailMatches(IntentDetail detail, String intent, String contextType, String resultType) { + // Match intent + if (intent != null && detail.getIntent() != null && !intent.equals(detail.getIntent())) { + return false; + } + + // Match context type (optional) + if (contextType != null && detail.getContext() != null && !contextType.equals(detail.getContext())) { + return false; + } + + // Match result type (optional, with generic handling) + if (resultType != null) { + String detailResultType = detail.getResultType(); + if (detailResultType == null) { + return false; + } + + // Handle generic types + if (resultType.contains("<")) { + if (!resultType.equals(detailResultType)) { + return false; + } + } else { + String actualType = removeGenericType(detailResultType); + if (!resultType.equals(actualType)) { + return false; + } + } + } + + return true; + } + + private String removeGenericType(String type) { + int start = type.indexOf('<'); + return start > 0 ? type.substring(0, start) : type; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetAppMetadataResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetAppMetadataResponse.java new file mode 100644 index 00000000..11151933 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetAppMetadataResponse.java @@ -0,0 +1,52 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to getAppMetadata requests. + */ +public class GetAppMetadataResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "getAppMetadataRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + Map app = (Map) msgPayload.get("app"); + + String appId = app != null ? (String) app.get("appId") : "unknown"; + + Map appMetadata = new HashMap<>(); + appMetadata.put("appId", appId); + appMetadata.put("name", "Metadata Name"); + appMetadata.put("description", "Metadata Description"); + + Map payload = new HashMap<>(); + payload.put("appMetadata", appMetadata); + + Map response = new HashMap<>(); + response.put("type", "getAppMetadataResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetInfoResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetInfoResponse.java new file mode 100644 index 00000000..77d07a5c --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetInfoResponse.java @@ -0,0 +1,59 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to getInfo requests. + */ +public class GetInfoResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "getInfoRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + Map appMetadata = new HashMap<>(); + appMetadata.put("appId", "cucumber-app"); + appMetadata.put("instanceId", "cucumber-instance"); + + Map optionalFeatures = new HashMap<>(); + optionalFeatures.put("DesktopAgentBridging", false); + optionalFeatures.put("OriginatingAppMetadata", true); + optionalFeatures.put("UserChannelMembershipAPIs", true); + + Map implementationMetadata = new HashMap<>(); + implementationMetadata.put("appMetadata", appMetadata); + implementationMetadata.put("fdc3Version", "2.0"); + implementationMetadata.put("optionalFeatures", optionalFeatures); + implementationMetadata.put("provider", "cucumber-provider"); + implementationMetadata.put("providerVersion", "test"); + + Map payload = new HashMap<>(); + payload.put("implementationMetadata", implementationMetadata); + + Map response = new HashMap<>(); + response.put("type", "getInfoResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetOrCreateChannelResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetOrCreateChannelResponse.java new file mode 100644 index 00000000..fd48e8e3 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetOrCreateChannelResponse.java @@ -0,0 +1,69 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to getOrCreateChannel requests. + */ +public class GetOrCreateChannelResponse implements AutomaticResponse { + + private final Map channelTypes = new ConcurrentHashMap<>(); + + @Override + public boolean filter(String messageType) { + return "getOrCreateChannelRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + String channelId = (String) msgPayload.get("channelId"); + String type = "app"; + + String existingType = channelTypes.get(channelId); + + Map payload = new HashMap<>(); + + if (existingType != null && !existingType.equals(type)) { + // channel already exists with different type + payload.put("error", "AccessDenied"); + } else { + channelTypes.put(channelId, type); + + Map channel = new HashMap<>(); + channel.put("id", channelId); + channel.put("type", type); + + Map displayMetadata = new HashMap<>(); + displayMetadata.put("name", "The " + channelId + " Channel"); + displayMetadata.put("color", "cerulean blue"); + displayMetadata.put("glyph", "triangle"); + channel.put("displayMetadata", displayMetadata); + + payload.put("channel", channel); + } + + Map response = new HashMap<>(); + response.put("type", "getOrCreateChannelResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetUserChannelsResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetUserChannelsResponse.java new file mode 100644 index 00000000..148cfd08 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetUserChannelsResponse.java @@ -0,0 +1,60 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to getUserChannels requests. + */ +public class GetUserChannelsResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "getUserChannelsRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + + List> userChannels = new ArrayList<>(); + for (String channelId : messaging.getChannelState().keySet()) { + Map channel = new HashMap<>(); + channel.put("id", channelId); + channel.put("type", "user"); + + Map displayMetadata = new HashMap<>(); + displayMetadata.put("name", "The " + channelId + " channel"); + displayMetadata.put("color", "red"); + displayMetadata.put("glyph", "triangle"); + channel.put("displayMetadata", displayMetadata); + + userChannels.add(channel); + } + + Map payload = new HashMap<>(); + payload.put("userChannels", userChannels); + + Map response = new HashMap<>(); + response.put("type", "getUserChannelsResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/IntentResultResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/IntentResultResponse.java new file mode 100644 index 00000000..d52bcff8 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/IntentResultResponse.java @@ -0,0 +1,60 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; + +/** + * Responds to intentResult requests. + */ +public class IntentResultResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "intentResultRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + + // Store the intent result from the request + Map intentResultPayload = (Map) msgPayload.get("intentResult"); + if (intentResultPayload != null) { + PossibleIntentResult result = new PossibleIntentResult(); + + Map contextMap = (Map) intentResultPayload.get("context"); + if (contextMap != null) { + Context context = Context.fromMap(contextMap); + result.setContext(context); + } + + // Note: channel handling would require a Channel implementation + // For now, we just store what we can + + messaging.setIntentResult(result); + } + + Map response = new HashMap<>(); + response.put("type", "intentResultResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/OpenResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/OpenResponse.java new file mode 100644 index 00000000..e67c999f --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/OpenResponse.java @@ -0,0 +1,66 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; + +/** + * Responds to open requests. + */ +public class OpenResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "openRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + Map app = (Map) msgPayload.get("app"); + + String appId = app != null ? (String) app.get("appId") : null; + + Map payload = new HashMap<>(); + + // Find the app in intent details + IntentDetail found = null; + for (IntentDetail detail : messaging.getIntentDetails()) { + if (detail.getApp() != null && appId != null && + appId.equals(detail.getApp().getAppId())) { + found = detail; + break; + } + } + + if (found != null && found.getApp() != null) { + Map appIdentifier = new HashMap<>(); + appIdentifier.put("appId", found.getApp().getAppId()); + appIdentifier.put("instanceId", "abc123"); + payload.put("appIdentifier", appIdentifier); + } else { + payload.put("error", "AppNotFound"); + } + + Map response = new HashMap<>(); + response.put("type", "openResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentForContextResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentForContextResponse.java new file mode 100644 index 00000000..527bbe0c --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentForContextResponse.java @@ -0,0 +1,221 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; +import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; + +/** + * Responds to raiseIntentForContext requests. + */ +public class RaiseIntentForContextResponse implements AutomaticResponse { + + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + @Override + public boolean filter(String messageType) { + return "raiseIntentForContextRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + + Map context = (Map) msgPayload.get("context"); + String contextType = context != null ? (String) context.get("type") : null; + Map targetApp = (Map) msgPayload.get("app"); + + PossibleIntentResult intentResult = messaging.getIntentResult(); + + if (intentResult == null) { + // Figure out response based on app details + List matching = findMatchingIntents(messaging, contextType, targetApp); + Map response = createRaiseIntentForContextResponse(meta, matching, messaging); + scheduleReceive(messaging, response); + } else if (!intentResult.isTimeout()) { + // Send pre-set intent resolution + Map response = createCannedResponse(meta, messaging); + scheduleReceive(messaging, response); + + // Then send the result response + if (intentResult.getError() == null) { + Map resultResponse = createRaiseIntentResultResponse(meta, messaging); + if (resultResponse != null) { + scheduler.schedule(() -> { + messaging.receive(resultResponse, null); + }, 300, TimeUnit.MILLISECONDS); + } + } + } + + return CompletableFuture.completedFuture(null); + } + + private List findMatchingIntents(TestMessaging messaging, String contextType, + Map targetApp) { + List matching = new ArrayList<>(); + for (IntentDetail detail : messaging.getIntentDetails()) { + boolean matches = true; + + // Match context type - null is a wildcard that matches anything + // If either contextType (from request) or detail.getContext() is null, it matches + if (contextType != null && detail.getContext() != null && !contextType.equals(detail.getContext())) { + matches = false; + } + + // Match target app if specified + if (matches && targetApp != null && detail.getApp() != null) { + String targetAppId = (String) targetApp.get("appId"); + String targetInstanceId = (String) targetApp.get("instanceId"); + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppId())) { + matches = false; + } + if (matches && targetInstanceId != null && + detail.getApp().getInstanceId() != null && + !targetInstanceId.equals(detail.getApp().getInstanceId())) { + matches = false; + } + } + + if (matches) { + matching.add(detail); + } + } + return matching; + } + + private Map createRaiseIntentForContextResponse(Map meta, + List relevant, + TestMessaging messaging) { + Map payload = new HashMap<>(); + + if (relevant.isEmpty()) { + payload.put("error", "NoAppsFound"); + } else if (relevant.size() == 1 && relevant.get(0).getIntent() != null && relevant.get(0).getApp() != null) { + IntentDetail detail = relevant.get(0); + Map source = new HashMap<>(); + source.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { source.put("instanceId", detail.getApp().getInstanceId()); } + + Map resolution = new HashMap<>(); + resolution.put("intent", detail.getIntent()); + resolution.put("source", source); + payload.put("intentResolution", resolution); + } else { + // Multiple intents found - return appIntents for disambiguation + Set uniqueIntents = new LinkedHashSet<>(); + for (IntentDetail detail : relevant) { + if (detail.getIntent() != null) { + uniqueIntents.add(detail.getIntent()); + } + } + + List> appIntents = new ArrayList<>(); + for (String intentName : uniqueIntents) { + Map intentInfo = new LinkedHashMap<>(); + intentInfo.put("name", intentName); + intentInfo.put("displayName", intentName); + + List> apps = new ArrayList<>(); + for (IntentDetail detail : relevant) { + if (intentName.equals(detail.getIntent()) && detail.getApp() != null) { + Map app = new LinkedHashMap<>(); + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } + apps.add(app); + } + } + + Map appIntent = new LinkedHashMap<>(); + appIntent.put("intent", intentInfo); + appIntent.put("apps", apps); + appIntents.add(appIntent); + } + + payload.put("appIntents", appIntents); + } + + Map response = new HashMap<>(); + response.put("type", "raiseIntentForContextResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } + + private Map createCannedResponse(Map meta, TestMessaging messaging) { + PossibleIntentResult result = messaging.getIntentResult(); + + Map payload = new HashMap<>(); + + if (result != null && result.getError() != null) { + payload.put("error", result.getError()); + } else { + Map source = new HashMap<>(); + source.put("appId", "some-app"); + source.put("instanceId", "abc123"); + + Map resolution = new HashMap<>(); + resolution.put("intent", "some-canned-intent"); + resolution.put("source", source); + payload.put("intentResolution", resolution); + } + + Map response = new HashMap<>(); + response.put("type", "raiseIntentForContextResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } + + private Map createRaiseIntentResultResponse(Map meta, TestMessaging messaging) { + PossibleIntentResult result = messaging.getIntentResult(); + if (result == null || result.getError() != null) { + return null; + } + + Map intentResult = new HashMap<>(); + if (result.getContext() != null) { + intentResult.put("context", result.getContext()); + } + if (result.getChannel() != null) { + Map channelMap = new HashMap<>(); + channelMap.put("id", result.getChannel().getId()); + channelMap.put("type", result.getChannel().getType()); + intentResult.put("channel", channelMap); + } + + Map payload = new HashMap<>(); + payload.put("intentResult", intentResult); + + Map response = new HashMap<>(); + response.put("type", "raiseIntentResultResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentResponse.java new file mode 100644 index 00000000..616f4c93 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentResponse.java @@ -0,0 +1,222 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; +import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; + +/** + * Responds to raiseIntent requests. + */ +public class RaiseIntentResponse implements AutomaticResponse { + + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + @Override + public boolean filter(String messageType) { + return "raiseIntentRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + Map meta = (Map) message.get("meta"); + Map msgPayload = (Map) message.get("payload"); + + String intent = (String) msgPayload.get("intent"); + Map context = (Map) msgPayload.get("context"); + String contextType = context != null ? (String) context.get("type") : null; + Map targetApp = (Map) msgPayload.get("app"); + + PossibleIntentResult intentResult = messaging.getIntentResult(); + + if (intentResult == null) { + // Figure out response based on app details (like FindIntent) + List matching = findMatchingIntents(messaging, intent, contextType, targetApp); + Map response = createRaiseIntentResponse(meta, message, matching, messaging); + scheduleReceive(messaging, response); + } else if (!intentResult.isTimeout()) { + // Send pre-set intent resolution + Map response = createCannedRaiseIntentResponse(meta, message, messaging); + scheduleReceive(messaging, response); + + // Then send the result response + if (intentResult.getError() == null) { + Map resultResponse = createRaiseIntentResultResponse(meta, messaging); + if (resultResponse != null) { + scheduler.schedule(() -> { + messaging.receive(resultResponse, null); + }, 300, TimeUnit.MILLISECONDS); + } + } + } + + return CompletableFuture.completedFuture(null); + } + + private List findMatchingIntents(TestMessaging messaging, String intent, + String contextType, Map targetApp) { + List matching = new ArrayList<>(); + for (IntentDetail detail : messaging.getIntentDetails()) { + boolean matches = true; + + // Match intent name + if (intent != null && detail.getIntent() != null && !intent.equals(detail.getIntent())) { + matches = false; + } + + // Match context type (optional) + if (matches && contextType != null && detail.getContext() != null && !contextType.equals(detail.getContext())) { + // Context type matching is optional + } + + // Match target app if specified + if (matches && targetApp != null && detail.getApp() != null) { + String targetAppId = (String) targetApp.get("appId"); + String targetInstanceId = (String) targetApp.get("instanceId"); + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppId())) { + matches = false; + } + if (matches && targetInstanceId != null && + detail.getApp().getInstanceId() != null && + !targetInstanceId.equals(detail.getApp().getInstanceId())) { + matches = false; + } + } + + if (matches) { + matching.add(detail); + } + } + return matching; + } + + @SuppressWarnings("unchecked") + private Map createRaiseIntentResponse(Map meta, Map message, + List relevant, TestMessaging messaging) { + Map msgPayload = (Map) message.get("payload"); + String intent = (String) msgPayload.get("intent"); + + Map payload = new HashMap<>(); + + if (relevant.isEmpty()) { + payload.put("error", "NoAppsFound"); + } else if (relevant.size() == 1 && relevant.get(0).getIntent() != null && relevant.get(0).getApp() != null) { + IntentDetail detail = relevant.get(0); + Map source = new HashMap<>(); + source.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { source.put("instanceId", detail.getApp().getInstanceId()); } + + Map resolution = new HashMap<>(); + resolution.put("intent", detail.getIntent()); + resolution.put("source", source); + payload.put("intentResolution", resolution); + } else { + // Multiple apps found - return appIntent for disambiguation + List> apps = new ArrayList<>(); + for (IntentDetail detail : relevant) { + if (detail.getApp() != null && detail.getApp().getAppId() != null) { + Map app = new HashMap<>(); + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } + apps.add(app); + } + } + + Map intentInfo = new HashMap<>(); + intentInfo.put("name", intent); + intentInfo.put("displayName", intent); + + Map appIntent = new HashMap<>(); + appIntent.put("intent", intentInfo); + appIntent.put("apps", apps); + payload.put("appIntent", appIntent); + } + + Map response = new HashMap<>(); + response.put("type", "raiseIntentResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } + + @SuppressWarnings("unchecked") + private Map createCannedRaiseIntentResponse(Map meta, + Map message, + TestMessaging messaging) { + PossibleIntentResult result = messaging.getIntentResult(); + Map msgPayload = (Map) message.get("payload"); + String intent = (String) msgPayload.get("intent"); + + Map payload = new HashMap<>(); + + if (result != null && result.getError() != null) { + payload.put("error", result.getError()); + } else { + Map source = new HashMap<>(); + source.put("appId", "some-app"); + source.put("instanceId", "abc123"); + + Map resolution = new HashMap<>(); + resolution.put("intent", intent); + resolution.put("source", source); + payload.put("intentResolution", resolution); + } + + Map response = new HashMap<>(); + response.put("type", "raiseIntentResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } + + private Map createRaiseIntentResultResponse(Map meta, TestMessaging messaging) { + PossibleIntentResult result = messaging.getIntentResult(); + if (result == null || result.getError() != null) { + return null; + } + + Map intentResult = new HashMap<>(); + if (result.getContext() != null) { + intentResult.put("context", result.getContext()); + } + if (result.getChannel() != null) { + Map channelMap = new HashMap<>(); + channelMap.put("id", result.getChannel().getId()); + channelMap.put("type", result.getChannel().getType()); + intentResult.put("channel", channelMap); + } + + Map payload = new HashMap<>(); + payload.put("intentResult", intentResult); + if (result.getResultMetadata() != null) { + payload.put("resultMetadata", messaging.getConverter().convertValue( + result.getResultMetadata(), Map.class)); + } + + Map response = new HashMap<>(); + response.put("type", "raiseIntentResultResponse"); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + return response; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RegisterListenersResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RegisterListenersResponse.java new file mode 100644 index 00000000..0cd4f0c2 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RegisterListenersResponse.java @@ -0,0 +1,50 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to listener registration requests. + */ +public class RegisterListenersResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "addContextListenerRequest".equals(messageType) || + "addIntentListenerRequest".equals(messageType) || + "addEventListenerRequest".equals(messageType) || + "privateChannelAddEventListenerRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + String type = (String) message.get("type"); + String responseType = type.replace("Request", "Response"); + + Map meta = (Map) message.get("meta"); + + Map payload = new HashMap<>(); + payload.put("listenerUUID", UUID.randomUUID().toString()); + + Map response = new HashMap<>(); + response.put("type", responseType); + response.put("meta", createResponseMeta(meta)); + response.put("payload", payload); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ResponseSupport.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ResponseSupport.java new file mode 100644 index 00000000..ac94ced7 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ResponseSupport.java @@ -0,0 +1,45 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Support utilities for creating test responses. + */ +public class ResponseSupport { + + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); + + /** + * Creates response metadata from request metadata. + */ + public static Map createResponseMeta(Map requestMeta) { + Map meta = new HashMap<>(); + meta.put("requestUuid", requestMeta.get("requestUuid")); + meta.put("responseUuid", UUID.randomUUID().toString()); + meta.put("source", requestMeta.get("source")); + meta.put("timestamp", Instant.now().toString()); + return meta; + } + + /** + * Schedules a response to be sent after a short delay. + * This simulates async message delivery. + */ + public static void scheduleReceive(TestMessaging messaging, Map response) { + scheduler.schedule(() -> { + messaging.receive(response, null); + }, 100, TimeUnit.MILLISECONDS); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/UnsubscribeListenersResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/UnsubscribeListenersResponse.java new file mode 100644 index 00000000..12370640 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/UnsubscribeListenersResponse.java @@ -0,0 +1,46 @@ +/** + * Copyright FINOS and its Contributors + */ +package org.finos.fdc3.proxy.support.responses; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.createResponseMeta; +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.scheduleReceive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.proxy.support.TestMessaging; + +/** + * Responds to listener unsubscribe requests. + */ +public class UnsubscribeListenersResponse implements AutomaticResponse { + + @Override + public boolean filter(String messageType) { + return "contextListenerUnsubscribeRequest".equals(messageType) || + "intentListenerUnsubscribeRequest".equals(messageType) || + "eventListenerUnsubscribeRequest".equals(messageType) || + "privateChannelUnsubscribeEventListenerRequest".equals(messageType); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage action(Map message, TestMessaging messaging) { + String type = (String) message.get("type"); + String responseType = type.replace("Request", "Response"); + + Map meta = (Map) message.get("meta"); + + Map response = new HashMap<>(); + response.put("type", responseType); + response.put("meta", createResponseMeta(meta)); + response.put("payload", new HashMap<>()); + + scheduleReceive(messaging, response); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java new file mode 100644 index 00000000..09f55d50 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java @@ -0,0 +1,73 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.proxy.world; + +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.proxy.support.TestMessaging; + +import io.github.robmoffat.world.PropsWorld; + +/** + * Custom Cucumber World for agent-proxy tests. + * Extends PropsWorld and adds messaging support. + * + * Bean configuration in TestSpringConfig ensures the same instance is shared + * across all step definition classes within a scenario. + */ +public class CustomWorld extends PropsWorld { + + private TestMessaging messaging; + + /** + * standard-cucumber-steps stores invocation counters as {@link Runnable}, but FDC3 + * event listener APIs require {@link EventHandler}. Adapt on lookup so feature + * files can keep the canonical step wording unchanged. + */ + @Override + public Object get(String key) { + return adaptInvocationCounter(super.get(key)); + } + + @Override + public Object get(Object key) { + if (key instanceof String) { + return get((String) key); + } + return adaptInvocationCounter(super.get(key)); + } + + private static Object adaptInvocationCounter(Object value) { + if (value instanceof Runnable && !(value instanceof EventHandler)) { + Runnable runnable = (Runnable) value; + return (EventHandler) event -> runnable.run(); + } + return value; + } + + public TestMessaging getMessaging() { + return messaging; + } + + public void setMessaging(TestMessaging messaging) { + this.messaging = messaging; + } + + public boolean hasMessaging() { + return messaging != null; + } +} + diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/README.md b/fdc3-agent-proxy/src/test/resources/temporary-features/README.md new file mode 100644 index 00000000..53b4f6c7 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/README.md @@ -0,0 +1 @@ +These should be pulled in from the @finos/fdc3-agent-proxy instead of just being copied in here but that project doesn't export the features yet. \ No newline at end of file diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature new file mode 100644 index 00000000..5da42e88 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature @@ -0,0 +1,129 @@ +Feature: Channel Listeners Support + + Background: Desktop Agent API + Given schemas loaded + Given User Channels one, two and three + Given A Desktop Agent in "api1" + Given "instrumentMessageOne" is a BroadcastEvent message on channel "channel-name" with context "fdc3.instrument" + Given "countryMessageOne" is a BroadcastEvent message on channel "channel-name" with context "fdc3.country" + Given "instrumentContext" is a "fdc3.instrument" context + Given "resultHandler" pipes context to "contexts" + + Scenario: Configuring two context listeners should mean they both pick up data + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + | AAPL | fdc3.instrument | Apple | + Then messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | channel-name | {null} | getOrCreateChannelRequest | + | channel-name | fdc3.instrument | addContextListenerRequest | + | channel-name | fdc3.instrument | addContextListenerRequest | + + Scenario: Unsubscribing a context listener prevents it collecting data. + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I call "{result}" with "unsubscribe" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is empty + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | channel-name | {null} | getOrCreateChannelRequest | + | channel-name | fdc3.instrument | addContextListenerRequest | + | {null} | {null} | contextListenerUnsubscribeRequest | + + Scenario: I can create a listener which listens for any context type + In this version we are using the deprecated 1-arg approach + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using argument "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + And messaging receives "{countryMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | type | name | + | fdc3.instrument | Apple | + | fdc3.country | Sweden | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | channel-name | {null} | getOrCreateChannelRequest | + | channel-name | {null} | addContextListenerRequest | + + Scenario: I can create a listener which listens for any context type + In this version we are using the non-deprecated 2 args approach + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "{null}" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + And messaging receives "{countryMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | type | name | + | fdc3.instrument | Apple | + | fdc3.country | Sweden | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | channel-name | {null} | getOrCreateChannelRequest | + | channel-name | {null} | addContextListenerRequest | + + Scenario: Passing invalid arguments to an app channel's addContextListener fn throws an error + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + # Specific error message not tested as its not currently standardized + # TODO: Fix when #1490 is resolved + And I call "{channel1}" with "addContextListener" using arguments "{true}" and "{resultHandler}" + Then "{result}" is an error + And I call "{channel1}" with "addContextListener" using arguments "{null}" and "{true}" + Then "{result}" is an error + + Scenario: Destructured channel methods - broadcast and addContextListener + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | channel-name | {null} | getOrCreateChannelRequest | + | channel-name | fdc3.instrument | addContextListenerRequest | + + Scenario: Destructured getCurrentContext after broadcast + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "broadcast", "getCurrentContext" from "{channel1}" + And I call destructured "broadcast" using argument "{instrumentContext}" + And I call destructured "getCurrentContext" using argument "fdc3.instrument" + Then "{result}" is an object with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: Destructured listener receives filtered context + Given "countryContext" is a "fdc3.country" context + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: App channel context listener receives source metadata + Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | + | cucumber-app | cucumber-instance | \ No newline at end of file diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature new file mode 100644 index 00000000..ebcc4f71 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature @@ -0,0 +1,35 @@ +Feature: Desktop Agent Information + + Background: Desktop Agent API + Given schemas loaded + And A Desktop Agent in "api" + And app "chipShop/c1" + + Scenario: Getting App metadata + When I call "{api}" with "getAppMetadata" using argument "{c1}" + Then "{result}" is an object with the following contents + | appId | name | description | + | chipShop | Metadata Name | Metadata Description | + And messaging will have posts + | payload.app.appId | payload.app.instanceId | matches_type | + | chipShop | c1 | getAppMetadataRequest | + + Scenario: Getting own info + When I call "{api}" with "getInfo" + Then "{result}" is an object with the following contents + | fdc3Version | provider | + | 2.0 | cucumber-provider | + And "{result.appMetadata}" is an object with the following contents + | appId | instanceId | + | cucumber-app | cucumber-instance | + + Scenario: Getting instance information + When I call "{api}" with "findInstances" using argument "{c1}" + Then "{result}" is an array of objects with the following contents + | appId | instanceId | + | One | 1 | + | Two | 2 | + | Three | 3 | + And messaging will have posts + | payload.app.appId | payload.app.instanceId | matches_type | + | chipShop | c1 | findInstancesRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature new file mode 100644 index 00000000..889ceded --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature @@ -0,0 +1,70 @@ +Feature: Broadcasting + + Background: Desktop Agent API + Given schemas loaded + Given User Channels one, two and three + Given A Desktop Agent in "api" + Given "instrumentMessageOne" is a BroadcastEvent message on channel "channel-name" with context "fdc3.instrument" + Given "countryMessageOne" is a BroadcastEvent message on channel "channel-name" with context "fdc3.country" + Given "instrumentContext" is a "fdc3.instrument" context + + Scenario: Broadcasting on a named app channel + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "broadcast" using argument "{instrumentContext}" + Then messaging will have posts + | payload.channelId | payload.context.type | payload.context.name | matches_type | + | channel-name | fdc3.instrument | Apple | broadcastRequest | + + Scenario: Broadcasting using the api directly, with no user channel set + When I call "{api}" with "broadcast" using argument "{instrumentContext}" + Then messaging will have posts + | payload.channelId | payload.context.type | payload.context.name | + + Scenario: Broadcasting using the api directly, with user channel set + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "broadcast" using argument "{instrumentContext}" + Then messaging will have posts + | payload.channelId | payload.context.type | payload.context.name | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | one | fdc3.instrument | Apple | broadcastRequest | + + Scenario: Context listener receives source metadata + Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | + | cucumber-app | cucumber-instance | + + Scenario: Context listener receives full metadata including signature and custom + Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" + Given "fullMetadataMessage" is a BroadcastEvent message on channel "channel-name" with context "fdc3.instrument" and metadata + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{fullMetadataMessage}" + Then "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | signature.signature | signature.protected | custom.region | + | cucumber-app | cucumber-instance | test-sig (signature part) | test-sig (protected part) | EMEA | + + Scenario: getCurrentContextWithMetadata returns context and metadata + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "broadcast" using argument "{instrumentContext}" + And I call "{channel1}" with "getCurrentContextWithMetadata" using argument "fdc3.instrument" + Then "{result}" is an object with the following contents + | context.type | context.name | metadata.source.appId | metadata.traceId | metadata.signature.signature | metadata.signature.protected | metadata.custom.key | + | fdc3.instrument | Apple | test-app | test-trace-id | test-signature (signature part) | test-signature (protected part) | value | + + Scenario: getCurrentContextWithMetadata returns null for empty channel + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "getCurrentContextWithMetadata" using argument "fdc3.instrument" + Then "{result}" is null diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature new file mode 100644 index 00000000..fce0f0aa --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature @@ -0,0 +1,74 @@ +Feature: Basic Intents Support + + Background: Desktop Agent API + Given schemas loaded + And A Desktop Agent in "api" + And app "chipShop/c1" resolves intent "OrderFood" with result type "void" + And app "chipShop/c2" resolves intent "OrderFood" with result type "channel" + And app "library/l1" resolves intent "BorrowBooks" with result type "channel" + And app "bank/b1" resolves intent "Buy" with context "fdc3.instrument" and result type "fdc3.order" + And app "bank/b1" resolves intent "Sell" with context "fdc3.instrument" and result type "fdc3.order" + And app "travelAgent/t1" resolves intent "Buy" with context "fdc3.currency" and result type "fdc3.order" + And "instrumentContext" is a "fdc3.instrument" context + And "crazyContext" is a "fdc3.unsupported" context + + Scenario: Find Intent can return the same intent with multiple apps + When I call "{api}" with "findIntent" using argument "Buy" + Then "{result.intent}" is an object with the following contents + | name | + | Buy | + And "{result.apps}" is an array of objects with the following contents + | appId | instanceId | + | bank | b1 | + | travelAgent | t1 | + And messaging will have posts + | payload.intent | matches_type | + | Buy | findIntentRequest | + + Scenario: Find Intent can return an error when an intent doesn't match + When I call "{api}" with "findIntent" using argument "Bob" + Then "{result}" is an error with message "NoAppsFound" + And messaging will have posts + | payload.intent | matches_type | + | Bob | findIntentRequest | + + Scenario: Find Intent can filter by a context type + When I call "{api}" with "findIntent" using arguments "Buy" and "{instrumentContext}" + Then "{result.intent}" is an object with the following contents + | name | + | Buy | + And "{result.apps}" is an array of objects with the following contents + | appId | + | bank | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | Buy | fdc3.instrument | AAPL | findIntentRequest | + + Scenario: Find Intent can filter by generic result type + When I call "{api}" with "findIntent" using arguments "OrderFood", "{empty}", and "channel" + Then "{result.intent}" is an object with the following contents + | name | + | OrderFood | + And "{result.apps}" is an array of objects with the following contents + | appId | instanceId | + | chipShop | c2 | + And messaging will have posts + | payload.intent | payload.resultType | matches_type | + | OrderFood | channel | findIntentRequest | + + Scenario: Find Intents By Context + When I call "{api}" with "findIntentsByContext" using argument "{instrumentContext}" + Then "{result}" is an array of objects with the following contents + | intent.name | apps[0].appId | apps.length | + | Buy | bank | 1 | + | Sell | bank | 1 | + And messaging will have posts + | payload.context.type | payload.context.id.ticker | matches_type | + | fdc3.instrument | AAPL | findIntentsByContextRequest | + + Scenario: Find Intents By Context can return an error when an intent doesn't match + When I call "{api}" with "findIntentsByContext" using argument "{crazyContext}" + Then "{result}" is an error with message "NoAppsFound" + And messaging will have posts + | payload.context.type | payload.context.bogus | matches_type | + | fdc3.unsupported | {true} | findIntentsByContextRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/heartbeat.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/heartbeat.feature new file mode 100644 index 00000000..0123dd29 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/heartbeat.feature @@ -0,0 +1,18 @@ +Feature: Heartbeats + + Background: Desktop Agent API + Given A Desktop Agent in "api" + And schemas loaded + + Scenario: Send A Heartbeat + When messaging receives a heartbeat event + And messaging will have posts + | matches_type | + | heartbeatAcknowledgementRequest | + + Scenario: Saying Goodbye + When I call "{api}" with "disconnect" + And we wait for a period of "100" ms + Then messaging will have posts + | matches_type | + | WCP6Goodbye | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature new file mode 100644 index 00000000..bca04b89 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature @@ -0,0 +1,42 @@ +Feature: Intent Listeners + + Background: Desktop Agent API + Given schemas loaded + And "instrumentContext" is a "fdc3.instrument" context + And A Desktop Agent in "api1" + And "intentMessageOne" is a intentEvent message with intent "BuyStock" and context "{instrumentContext}" + + Scenario: Intent Listeners Work + Given "resultHandler" pipes intent to "intents" + When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + And messaging receives "{intentMessageOne}" + Then "{intents}" is an array of objects with the following contents + | context.type | context.name | metadata.source.appId | + | fdc3.instrument | Apple | cucumber-app | + And messaging will have posts + | type | + | intentResultRequest | + + Scenario: Intent Listeners Can Return Results (Context) + Given "resultHandler" returns a context item + When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + And messaging receives "{intentMessageOne}" + Then messaging will have posts + | type | payload.intentResult.context.type | payload.intentResolution.intent | + | intentResultRequest | fdc3.returned-intent | {empty} | + + Scenario: Intent Listeners Can Return Results (Channel) + Given "resultHandler" returns a channel + When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + And messaging receives "{intentMessageOne}" + Then messaging will have posts + | type | payload.intentResult.channel.type | payload.intentResult.channel.id | + | intentResultRequest | private | some-channel-id | + + Scenario: Intent Listeners Can Return A Void Result + Given "resultHandler" returns a void promise + When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + And messaging receives "{intentMessageOne}" + Then messaging will have posts + | type | payload.intentResult.channel | payload.intentResult.context | + | intentResultRequest | {empty} | {empty} | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature new file mode 100644 index 00000000..266232d0 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature @@ -0,0 +1,160 @@ +Feature: Intents Can Return Different Results + + Background: Desktop Agent API + Given schemas loaded + And A Desktop Agent in "api" + And app "chipShop/c1" resolves intent "OrderFood" + And "instrumentContext" is a "fdc3.instrument" context + + Scenario: Raise Intent times out + Given Raise Intent times out + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + Then "{result}" is an error with message "ApiTimeout" + + Scenario: Raise Intent Fails With An Error + Given Raise Intent will throw a "TargetAppUnavailable" error + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + Then "{result}" is an error with message "TargetAppUnavailable" + + Scenario: void is returned in the result + Given Raise Intent returns no result + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResult" + Then "{result}" is undefined + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Raising An intent With The App Parameter + When I call "{api}" with "raiseIntent" using arguments "OrderFood", "{instrumentContext}", and "{c1}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | intent | + | chipShop | c1 | OrderFood | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | payload.app.appId | payload.app.instanceId | matches_type | + | OrderFood | fdc3.instrument | AAPL | chipShop | c1 | raiseIntentRequest | + + Scenario: Context Data is returned in the result + Given Raise Intent returns a context of "{instrumentContext}" + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResult" + Then "{result}" is an object with the following contents + | type | name | + | fdc3.instrument | Apple | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: App Channel is returned in the result + Given Raise Intent returns an app channel + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResult" + Then "{result}" is an object with the following contents + | type | id | + | app | result-channel | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: User Channel is returned in the result + Given Raise Intent returns a user channel + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResult" + Then "{result}" is an object with the following contents + | type | id | + | user | result-channel | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Private Channel is returned in the result + Given Raise Intent returns a private channel + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResult" + Then "{result}" is an object with the following contents + | type | id | + | private | result-channel | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Destructured getResult returns context data + Given Raise Intent returns a context of "{instrumentContext}" + When I destructure method "raiseIntent" from "{api}" + And I call destructured "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I destructure method "getResult" from "{result}" + And I call destructured "getResult" + Then "{result}" is an object with the following contents + | type | name | + | fdc3.instrument | Apple | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Destructured raiseIntent with app parameter + When I destructure method "raiseIntent" from "{api}" + And I call destructured "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" and "{c1}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | intent | + | chipShop | c1 | OrderFood | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | payload.app.appId | payload.app.instanceId | matches_type | + | OrderFood | fdc3.instrument | AAPL | chipShop | c1 | raiseIntentRequest | + + Scenario: Destructured getResult returns app channel + Given Raise Intent returns an app channel + When I destructure method "raiseIntent" from "{api}" + And I call destructured "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I destructure method "getResult" from "{result}" + And I call destructured "getResult" + Then "{result}" is an object with the following contents + | type | id | + | app | result-channel | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Destructured getResult returns private channel + Given Raise Intent returns a private channel + When I destructure method "raiseIntent" from "{api}" + And I call destructured "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I destructure method "getResult" from "{result}" + And I call destructured "getResult" + Then "{result}" is an object with the following contents + | type | id | + | private | result-channel | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: getResultMetadata returns DA-generated metadata for a context result + Given Raise Intent returns a context of "{instrumentContext}" + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResultMetadata" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | some-app | abc123 | + + Scenario: getResultMetadata returns merged metadata when ContextWithMetadata is returned + Given Raise Intent returns a context of "{instrumentContext}" with traceId "my-trace-123" and signature "sig-abc" and antiReplay claims "1234/2345/result-jti" + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResultMetadata" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | traceId | signature.signature | signature.protected | antiReplay.iat | antiReplay.exp | antiReplay.jti | + | some-app | abc123 | my-trace-123 | sig-abc (signature part) | sig-abc (protected part) | {1234} | {2345} | result-jti | + + Scenario: getResultMetadata returns DA-generated metadata for a channel result + Given Raise Intent returns a private channel + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResultMetadata" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | some-app | abc123 | + + Scenario: getResultMetadata returns DA-generated metadata for a void result + Given Raise Intent returns no result + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResultMetadata" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | some-app | abc123 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature new file mode 100644 index 00000000..5aa2c5a9 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature @@ -0,0 +1,62 @@ +Feature: Desktop Agent Information + + Background: Desktop Agent API + Given A Desktop Agent in "api" + And schemas loaded + And app "chipShop/c1" + And "instrumentContext" is a "fdc3.instrument" context + + Scenario: Open An App + When I call "{api}" with "open" using arguments "{c1}" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | appId | instanceId | + | chipShop | abc123 | + And messaging will have posts + | payload.app.appId | payload.context.type | payload.context.id.ticker | matches_type | + | chipShop | fdc3.instrument | AAPL | openRequest | + + Scenario: Open An App Using App ID + When I call "{api}" with "open" using arguments "chipShop" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | appId | instanceId | + | chipShop | abc123 | + And messaging will have posts + | payload.app.appId | payload.context.type | payload.context.id.ticker | matches_type | + | chipShop | fdc3.instrument | AAPL | openRequest | + + Scenario: Opening a non-existent App + When I call "{api}" with "open" using arguments "nonExistent" and "{instrumentContext}" + Then "{result}" is an error with message "AppNotFound" + And messaging will have posts + | payload.app.appId | payload.context.type | payload.context.id.ticker | matches_type | + | nonExistent | fdc3.instrument | AAPL | openRequest | + + Scenario: Open An App - Destructured + When I destructure method "open" from "{api}" + And I call destructured "open" using arguments "{c1}" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | appId | instanceId | + | chipShop | abc123 | + And messaging will have posts + | payload.app.appId | payload.context.type | payload.context.id.ticker | matches_type | + | chipShop | fdc3.instrument | AAPL | openRequest | + + Scenario: Open An App Using App ID - Destructured + When I destructure method "open" from "{api}" + And I call destructured "open" using arguments "chipShop" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | appId | instanceId | + | chipShop | abc123 | + And messaging will have posts + | payload.app.appId | payload.context.type | payload.context.id.ticker | matches_type | + | chipShop | fdc3.instrument | AAPL | openRequest | + + Scenario: Open An App with null context and metadata + Given "openMetadata" is metadata with traceId "trace-open" and signature "sig-open" and antiReplay claims "1234/2345/open-jti" + When I call "{api}" with "open" using arguments "{c1}", "{null}", and "{openMetadata}" + Then "{result}" is an object with the following contents + | appId | instanceId | + | chipShop | abc123 | + And messaging will have posts + | payload.app.appId | payload.context | payload.metadata.traceId | payload.metadata.signature.signature | payload.metadata.signature.protected | payload.metadata.antiReplay.iat | payload.metadata.antiReplay.exp | payload.metadata.antiReplay.jti | matches_type | + | chipShop | {null} | trace-open | sig-open (signature part) | sig-open (protected part) | {1234} | {2345} | open-jti | openRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels-deprecated.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels-deprecated.feature new file mode 100644 index 00000000..0f36b274 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels-deprecated.feature @@ -0,0 +1,70 @@ +Feature: Basic Private Channels Support + + Background: Desktop Agent API + Given schemas loaded + And User Channels one, two and three + And A Desktop Agent in "api" + And I call "{api}" with "createPrivateChannel" + And I refer to "{result}" as "privateChannel" + And "instrumentMessageOne" is a BroadcastEvent message on channel "{privateChannel.id}" with context "fdc3.instrument" + + Scenario: Adding and then unsubscribing an "onAddContextListener" listener will send a notification of each event to the agent + Given "typesHandler" pipes types to "types" + When I call "{privateChannel}" with "onAddContextListener" using argument "{typesHandler}" + And I refer to "{result}" as "theListener" + And we wait for a period of "100" ms + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | addContextListener | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "onAddContextListener" on a given Private Channel to receive a notification + Given "onAddContextListenerMessage" is a PrivateChannelOnAddContextListenerEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + And "typesHandler" pipes types to "types" + When I call "{privateChannel}" with "onAddContextListener" using argument "{typesHandler}" + And we wait for a period of "100" ms + And messaging receives "{onAddContextListenerMessage}" + Then "{types}" is an array of strings with the following values + | value | + | fdc3.instrument | + + Scenario: Adding and then unsubscribing an "onUnsubscribe" listener will send a notification of each event to the agent + Given "typesHandler" pipes types to "types" + When I call "{privateChannel}" with "onUnsubscribe" using argument "{typesHandler}" + And we wait for a period of "100" ms + And I refer to "{result}" as "theListener" + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | unsubscribe | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "onUnsubscribe" on a given Private Channel to receive a notification + Given "onUnsubscribeListenerMessage" is a PrivateChannelOnUnsubscribeEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + And "typesHandler" pipes types to "types" + When I call "{privateChannel}" with "onUnsubscribe" using argument "{typesHandler}" + And we wait for a period of "100" ms + And messaging receives "{onUnsubscribeListenerMessage}" + Then "{types}" is an array of strings with the following values + | value | + | fdc3.instrument | + + Scenario: Adding and then unsubscribing an "onDisconnect" listener will send a notification of each event to the agent + Given "voidHandler" is a invocation counter into "count" + When I call "{privateChannel}" with "onDisconnect" using argument "{voidHandler}" + And I refer to "{result}" as "theListener" + And we wait for a period of "100" ms + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | disconnect | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "onDisconnect" on a given Private Channel to receive a notification + Given "onDisconnectListenerMessage" is a PrivateChannelOnDisconnectEvent message on channel "{privateChannel.id}" + And "voidHandler" is a invocation counter into "count" + When I call "{privateChannel}" with "onDisconnect" using argument "{voidHandler}" + And we wait for a period of "100" ms + And messaging receives "{onDisconnectListenerMessage}" + Then "{count}" is "1" diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature new file mode 100644 index 00000000..e937fdac --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature @@ -0,0 +1,150 @@ +Feature: Basic Private Channels Support + + Background: Desktop Agent API + Given schemas loaded + And User Channels one, two and three + And A Desktop Agent in "api" + And I call "{api}" with "createPrivateChannel" + And I refer to "{result}" as "privateChannel" + And "instrumentMessageOne" is a BroadcastEvent message on channel "{privateChannel.id}" with context "fdc3.instrument" + + Scenario: Adding and then unsubscribing a context listener will send a notification of each event to the agent + Given "contextHandler" pipes context to "context" + When I call "{privateChannel}" with "addContextListener" using arguments "fdc3.instrument" and "{contextHandler}" + And I refer to "{result}" as "theListener" + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | + | addContextListenerRequest | {privateChannel.id} | fdc3.instrument | {null} | addContextListenerRequest | + | contextListenerUnsubscribeRequest | {null} | {null} | {theListener.id} | contextListenerUnsubscribeRequest | + + Scenario: Adding a Context Listener on a given Private Channel to receive a notification + Given "resultHandler" pipes context to "contexts" + When I call "{privateChannel}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: Private channel context listener receives source metadata + Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" + When I call "{privateChannel}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | + | cucumber-app | cucumber-instance | + + Scenario: Adding and then unsubscribing an "onAddContextListener" listener will send a notification of each event to the agent + Given "typesHandler" pipes events to "types" + When I call "{privateChannel}" with "addEventListener" using arguments "addContextListener" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And we wait for a period of "100" ms + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | addContextListener | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "addContextListener" event handler on a given Private Channel to receive a notification + Given "onAddContextListenerMessage" is a PrivateChannelOnAddContextListenerEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + And "typesHandler" pipes events to "types" + When I call "{privateChannel}" with "addEventListener" using arguments "addContextListener" and "{typesHandler}" + And we wait for a period of "100" ms + And messaging receives "{onAddContextListenerMessage}" + Then "{types}" is an array of objects with the following contents + | contextType | + | fdc3.instrument | + + Scenario: Adding and then unsubscribing an "onUnsubscribe" listener will send a notification of each event to the agent + Given "typesHandler" pipes events to "types" + When I call "{privateChannel}" with "addEventListener" using arguments "unsubscribe" and "{typesHandler}" + And we wait for a period of "100" ms + And I refer to "{result}" as "theListener" + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | unsubscribe | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "unsubscribe" event handler on a given Private Channel to receive a notification + Given "onUnsubscribeListenerMessage" is a PrivateChannelOnUnsubscribeEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + And "typesHandler" pipes events to "types" + When I call "{privateChannel}" with "addEventListener" using arguments "unsubscribe" and "{typesHandler}" + And we wait for a period of "100" ms + And messaging receives "{onUnsubscribeListenerMessage}" + Then "{types}" is an array of objects with the following contents + | contextType | + | fdc3.instrument | + + Scenario: Adding an event handler for all events on a given Private Channel to receive a notification + Given "onAddContextListenerMessage" is a PrivateChannelOnAddContextListenerEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + Given "onUnsubscribeListenerMessage" is a PrivateChannelOnUnsubscribeEvent message on channel "{privateChannel.id}" with contextType as "fdc3.instrument" + Given "onDisconnectListenerMessage" is a PrivateChannelOnDisconnectEvent message on channel "{privateChannel.id}" + And "typesHandler" pipes events to "types" + And I call "{privateChannel}" with "addEventListener" using arguments "{null}" and "{typesHandler}" + And we wait for a period of "100" ms + And messaging receives "{onAddContextListenerMessage}" + And messaging receives "{onUnsubscribeListenerMessage}" + And messaging receives "{onDisconnectListenerMessage}" + Then "{types}" is an array of objects with length "3" + + Scenario: Adding and then unsubscribing an "disconnect" listener will send a notification of each event to the agent + Given "voidHandler" is a invocation counter into "count" + When I call "{privateChannel}" with "addEventListener" using arguments "disconnect" and "{voidHandler}" + And I refer to "{result}" as "theListener" + And we wait for a period of "100" ms + And I call "{theListener}" with "unsubscribe" + Then messaging will have posts + | type | payload.listenerType | payload.privateChannelId | payload.listenerUUID | matches_type | + | privateChannelAddEventListenerRequest | disconnect | {privateChannel.id} | {null} | privateChannelAddEventListenerRequest | + | privateChannelUnsubscribeEventListenerRequest | {null} | {null} | {theListener.id} | privateChannelUnsubscribeEventListenerRequest | + + Scenario: Adding an "onDisconnect" on a given Private Channel to receive a notification + Given "onDisconnectListenerMessage" is a PrivateChannelOnDisconnectEvent message on channel "{privateChannel.id}" + And "voidHandler" is a invocation counter into "count" + When I call "{privateChannel}" with "addEventListener" using arguments "disconnect" and "{voidHandler}" + And we wait for a period of "100" ms + And messaging receives "{onDisconnectListenerMessage}" + Then "{count}" is "1" + + Scenario: I can broadcast context on a private channel + Given "instrumentContext" is a "fdc3.instrument" context + When I call "{privateChannel}" with "broadcast" using argument "{instrumentContext}" + Then messaging will have posts + | type | payload.channelId | payload.context.type | payload.context.name | matches_type | + | broadcastRequest | {privateChannel.id} | fdc3.instrument | Apple | broadcastRequest | + + Scenario: I disconnect from a private channel + And I call "{privateChannel}" with "disconnect" + And messaging will have posts + | payload.channelId | matches_type | + | {null} | createPrivateChannelRequest | + | {privateChannel.id} | privateChannelDisconnectRequest | + + Scenario: Destructured createPrivateChannel works correctly + When I destructure method "createPrivateChannel" from "{api}" + And I call destructured "createPrivateChannel" + And I refer to "{result}" as "destructuredPrivateChannel" + Then messaging will have posts + | payload.channelId | matches_type | + | {null} | createPrivateChannelRequest | + + Scenario: Destructured private channel methods work correctly + Given "resultHandler" pipes context to "contexts" + And I destructure methods "addContextListener", "broadcast" from "{privateChannel}" + And I call destructured "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I call destructured "broadcast" using argument "{instrumentContext}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: Destructured disconnect works correctly + When I destructure method "disconnect" from "{privateChannel}" + And I call destructured "disconnect" + Then messaging will have posts + | payload.channelId | matches_type | + | {privateChannel.id} | privateChannelDisconnectRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature new file mode 100644 index 00000000..d79bbc1f --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature @@ -0,0 +1,100 @@ +Feature: Basic Intents Support + + Background: Desktop Agent API + Given A Desktop Agent in "api" + And schemas loaded + And app "chipShop/c1" resolves intent "OrderFood" with result type "void" + And app "chipShop/c2" resolves intent "OrderFood" with result type "channel" + And app "bank/b1" resolves intent "Buy" with context "fdc3.instrument" and result type "fdc3.order" + And app "bank/b1" resolves intent "Sell" with context "fdc3.instrument" and result type "fdc3.order" + And app "travelAgent/t1" resolves intent "BookFlight" with context "fdc3.country" and result type "fdc3.order" + And app "notused/n1" resolves intent "Buy" with context "fdc3.cancel-me" and result type "fdc3.order" + And app "notused/n2" resolves intent "Buy" with context "fdc3.cancel-me" and result type "fdc3.order" + And "instrumentContext" is a "fdc3.instrument" context + And "countryContext" is a "fdc3.country" context + And "cancelContext" is a "fdc3.cancel-me" context + + Scenario: Raising an intent and invoking the intent resolver when it's not clear which intent is required + The intent resolver will just take the first matching application + that would resolve the intent. + + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | chipShop | c1 | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | payload.app.instanceId | matches_type | + | OrderFood | fdc3.instrument | AAPL | {null} | raiseIntentRequest | + | OrderFood | fdc3.instrument | AAPL | c1 | raiseIntentRequest | + + Scenario: Raising an intent and invoking the intent resolver, but the user cancels it. + When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{cancelContext}" + Then "{result}" is an error with message "UserCancelledResolution" + And messaging will have posts + | payload.intent | payload.context.type | matches_type | + | OrderFood | fdc3.cancel-me | raiseIntentRequest | + + Scenario: Raising Intent exactly right, so the resolver isn't required + When I call "{api}" with "raiseIntent" using arguments "Buy" and "{instrumentContext}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | bank | b1 | + And messaging will have posts + | payload.intent | payload.context.type | payload.context.id.ticker | matches_type | + | Buy | fdc3.instrument | AAPL | raiseIntentRequest | + + Scenario: Raising Intent By Context and invoking the intent resolver when it's not clear which intent is required + The intent resolver will just take the first matching application + that would resolve an intent. + + When I call "{api}" with "raiseIntentForContext" using argument "{instrumentContext}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | chipShop | c1 | + And messaging will have posts + | payload.context.type | payload.context.id.ticker | payload.app.instanceId | matches_type | + | fdc3.instrument | AAPL | {null} | raiseIntentForContextRequest | + | fdc3.instrument | AAPL | c1 | raiseIntentRequest | + + Scenario: Raising Intent By Context exactly right, so the resolver isn't required + When I call "{api}" with "raiseIntentForContext" using arguments "{countryContext}" and "{t1}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | travelAgent | t1 | + And messaging will have posts + | payload.context.type | payload.context.name | payload.app.appId | payload.app.instanceId | matches_type | + | fdc3.country | Sweden | travelAgent | t1 | raiseIntentForContextRequest | + + Scenario: Raising an intent and invoking the intent resolver, but the user cancels it. + When I call "{api}" with "raiseIntentForContext" using argument "{cancelContext}" + Then "{result}" is an error with message "UserCancelledResolution" + And messaging will have posts + | payload.context.type | matches_type | + | fdc3.cancel-me | raiseIntentForContextRequest | + + Scenario: Raising an intent with null app and metadata forwards traceId, signature, antiReplay and custom + Given "intentMetadata" is metadata with traceId "trace-123" and signature "sig-abc" and antiReplay claims "1234/2345/intent-null-app-jti" + When I call "{api}" with "raiseIntent" using arguments "Buy", "{instrumentContext}", "{null}", and "{intentMetadata}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | bank | b1 | + And messaging will have posts + | payload.intent | payload.context.type | payload.metadata.traceId | payload.metadata.signature.signature | payload.metadata.signature.protected | payload.metadata.antiReplay.iat | payload.metadata.antiReplay.exp | payload.metadata.antiReplay.jti | payload.metadata.custom.priority | matches_type | + | Buy | fdc3.instrument | trace-123 | sig-abc (signature part) | sig-abc (protected part) | {1234} | {2345} | intent-null-app-jti | high | raiseIntentRequest | + + Scenario: Raising an intent without metadata generates a traceId but omits signature and custom + When I call "{api}" with "raiseIntent" using arguments "Buy" and "{instrumentContext}" + And messaging will have posts + | payload.intent | payload.context.type | payload.metadata.signature.signature | payload.metadata.signature.protected | payload.metadata.custom | matches_type | + | Buy | fdc3.instrument | {null} | {null} | {null} | raiseIntentRequest | + + Scenario: Raising an intent for context with null app and metadata forwards metadata through resolver + Given "intentMetadata" is metadata with traceId "trace-456" and signature "sig-def" and antiReplay claims "1234/2345/intent-context-jti" + When I call "{api}" with "raiseIntentForContext" using arguments "{countryContext}", "{null}", and "{intentMetadata}" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | + | chipShop | c1 | + And messaging will have posts + | payload.context.type | payload.metadata.traceId | payload.metadata.signature.signature | payload.metadata.signature.protected | payload.metadata.antiReplay.iat | payload.metadata.antiReplay.exp | payload.metadata.antiReplay.jti | payload.metadata.custom.priority | payload.app.appId | matches_type | + | fdc3.country | trace-456 | sig-def (signature part) | sig-def (protected part) | {1234} | {2345} | intent-context-jti | high | {null} | raiseIntentForContextRequest | + | fdc3.country | trace-456 | sig-def (signature part) | sig-def (protected part) | {1234} | {2345} | intent-context-jti | high | chipShop | raiseIntentRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-selector.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-selector.feature new file mode 100644 index 00000000..cecbb46f --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-selector.feature @@ -0,0 +1,18 @@ +Feature: Updating User Channel State + + Background: + Given schemas loaded + Given User Channels one, two and three + And A Channel Selector in "selector" and a Desktop Agent in "api" + + Scenario: Selecting a channel updates the DA + When The first channel is selected via the channel selector in "selector" + And The second channel is selected via the channel selector in "selector" + Then messaging will have posts + | payload.channelId | matches_type | + | one | joinUserChannelRequest | + | two | joinUserChannelRequest | + And The channel is deselected via the channel selector in "selector" + Then messaging will have posts + | matches_type | + | leaveCurrentChannelRequest | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-sync.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-sync.feature new file mode 100644 index 00000000..ca11b6a1 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-sync.feature @@ -0,0 +1,44 @@ +Feature: Updating User Channel State + + Background: + Given schemas loaded + Given User Channels one, two and three + Given "instrumentContext" is a "fdc3.instrument" context + And "crazyContext" is a "fdc3.unsupported" context + And channel "one" has context "{instrumentContext}" + And channel "two" has context "{crazyContext}" + And A Desktop Agent in "api" + + Scenario: Joining A User Channel Receives Correct Context on Listener + Given "resultHandler" pipes context to "contexts" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" using argument "one" + And we wait for a period of "1000" ms + Then "{contexts}" is an array of objects with the following contents + | type | name | + | fdc3.instrument | Apple | + And I call "{api}" with "getCurrentChannel" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" + Then "{result}" is an object with the following contents + | type | name | + | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | one | fdc3.instrument | {null} | getCurrentContextRequest | + | one | fdc3.instrument | {null} | getCurrentContextRequest | + + Scenario: Changing User Channel Doesn't Receive Incorrect Context on Listener + Given "resultHandler" pipes context to "contexts" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" using argument "two" + Then "{contexts}" is an array of objects with the following contents + | type | name | + And I call "{api}" with "getCurrentChannel" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" + Then "{result}" is null + + Scenario: disconnection + When I call "{api}" with "disconnect" + Then "{result}" is undefined diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels-set-by-agent.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels-set-by-agent.feature new file mode 100644 index 00000000..c74bb6bc --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels-set-by-agent.feature @@ -0,0 +1,41 @@ +Feature: User Channels Support where the Desktop Agent puts the app on a channel + + Background: Desktop Agent API + Given User Channels one, two and three + Given schemas loaded + Given A Desktop Agent in "api" that puts apps on channel "one" + Given "instrumentMessageOne" is a BroadcastEvent message on channel "one" with context "fdc3.instrument" + + Scenario: Initial User Channel + At startup, the user channel should be set + + When I call "{api}" with "getCurrentChannel" + Then "{result}" is an object with the following contents + | id | type | displayMetadata.color | + | one | user | red | + + Scenario: Adding a Typed Listener on a given User Channel + Given "resultHandler" pipes context to "contexts" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | {null} | fdc3.instrument | addContextListenerRequest | + | one | fdc3.instrument | getCurrentContextRequest | + + Scenario: I should be able to leave a user channel, and not receive messages on it + Given "resultHandler" pipes context to "contexts" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I call "{api}" with "leaveCurrentChannel" + Then messaging will have posts + | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | + | {null} | fdc3.instrument | {null} | addContextListenerRequest | + | one | fdc3.instrument | {null} | getCurrentContextRequest | + | {null} | {null} | {null} | leaveCurrentChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature new file mode 100644 index 00000000..d0e4d9cf --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature @@ -0,0 +1,393 @@ +Feature: Basic User Channels Support + + Background: Desktop Agent API + Given User Channels one, two and three + Given schemas loaded + Given A Desktop Agent in "api" + Given "instrumentMessageOne" is a BroadcastEvent message on channel "one" with context "fdc3.instrument" + Given "countryMessageOne" is a BroadcastEvent message on channel "one" with context "fdc3.country" + Given "openMessage" is a BroadcastEvent message on channel "{null}" with context "fdc3.instrument" + Given "instrumentContext" is a "fdc3.instrument" context + Given "userChannelMessage1" is a channelChangedEvent message on channel "one" + Given "userChannelMessage2" is a channelChangedEvent message on channel "two" + Given "userChannelMessage3" is a channelChangedEvent message on channel "three" + Given "userChannelMessageBroken" is a channelChangedEvent message on channel "nonexistent" + + Scenario: List User Channels + There should be a selection of user channels to choose from + + When I call "{api}" with "getUserChannels" + Then "{result}" is an array of objects with the following contents + | id | type | displayMetadata.color | displayMetadata.glyph | displayMetadata.name | + | one | user | red | triangle | The one channel | + | two | user | red | triangle | The two channel | + | three | user | red | triangle | The three channel | + And messaging will have posts + | meta.source.appId | meta.source.instanceId | matches_type | + | cucumber-app | cucumber-instance | getUserChannelsRequest | + + Scenario: List User Channels via Deprecated API call + There should be a selection of user channels to choose from + + When I call "{api}" with "getSystemChannels" + Then "{result}" is an array of objects with the following contents + | id | type | displayMetadata.color | displayMetadata.glyph | displayMetadata.name | + | one | user | red | triangle | The one channel | + | two | user | red | triangle | The two channel | + | three | user | red | triangle | The three channel | + And messaging will have posts + | meta.source.appId | meta.source.instanceId | matches_type | + | cucumber-app | cucumber-instance | getUserChannelsRequest | + + Scenario: Initial User Channel + At startup, the user channel shouldn't be set + + When I call "{api}" with "getCurrentChannel" + Then "{result}" is null + And messaging will have posts + | meta.source.appId | meta.source.instanceId | matches_type | + | cucumber-app | cucumber-instance | getCurrentChannelRequest | + + Scenario: Changing Channel + You should be able to join a channel knowing it's ID. + + When I call "{api}" with "joinUserChannel" using argument "one" + When I call "{api}" with "getCurrentChannel" + Then "{result}" is an object with the following contents + | id | type | displayMetadata.color | + | one | user | red | + And messaging will have posts + | payload.channelId | matches_type | + | one | joinUserChannelRequest | + | {null} | getUserChannelsRequest | + + Scenario: Changing Channel via Deprecated API + You should be able to join a channel knowing it's ID. + + When I call "{api}" with "joinChannel" using argument "one" + When I call "{api}" with "getCurrentChannel" + Then "{result}" is an object with the following contents + | id | type | displayMetadata.color | + | one | user | red | + And messaging will have posts + | payload.channelId | matches_type | + | one | joinUserChannelRequest | + | {null} | getUserChannelsRequest | + + Scenario: Adding a Typed Listener on a given User Channel + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | one | {null} | joinUserChannelRequest | + | {null} | {null} | getUserChannelsRequest | + | {null} | fdc3.instrument | addContextListenerRequest | + | one | fdc3.instrument | getCurrentContextRequest | + + Scenario: Adding an Un-Typed Listener on a given User Channel + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "{empty}" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | one | {null} | joinUserChannelRequest | + | {null} | {null} | getUserChannelsRequest | + | {null} | {null} | addContextListenerRequest | + | one | {null} | getCurrentContextRequest | + + Scenario: Adding an Un-Typed Listener on a given User Channel (deprecated API) + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using argument "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | one | {null} | joinUserChannelRequest | + | {null} | {null} | getUserChannelsRequest | + | {null} | {null} | addContextListenerRequest | + | one | {null} | getCurrentContextRequest | + + Scenario: If you haven't joined a channel, your listener receives nothing + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is empty + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | {null} | fdc3.instrument | addContextListenerRequest | + + Scenario: After unsubscribing, my listener shouldn't receive any more messages + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{instrumentMessageOne}" + And I call "{theListener}" with "unsubscribe" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | {null} | fdc3.instrument | {null} | addContextListenerRequest | + | one | fdc3.instrument | {null} | getCurrentContextRequest | + | {null} | {null} | {theListener.id} | contextListenerUnsubscribeRequest | + + Scenario: I should be able to leave a user channel, and not receive messages on it + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I call "{api}" with "leaveCurrentChannel" + Then messaging will have posts + | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | {null} | fdc3.instrument | {null} | addContextListenerRequest | + | one | fdc3.instrument | {null} | getCurrentContextRequest | + | {null} | {null} | {null} | leaveCurrentChannelRequest | + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + + Scenario: Joining a user channel that doesn't exist throws an error + When I call "{api}" with "joinUserChannel" using argument "nonexistent" + Then "{result}" is an error with message "NoChannelFound" + + Scenario: Passing invalid arguments to a user channel's addContextListener fn throws an error + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "addContextListener" using arguments "{true}" and "{resultHandler}" + # Specific error message not tested as its not currently standardized + # TODO: Fix when #1490 is resolved + Then "{result}" is an error + And I call "{api}" with "addContextListener" using arguments "{null}" and "{true}" + Then "{result}" is an error + + Scenario: You can get the details of the last context type sent + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And I call "{api}" with "broadcast" using argument "{instrumentContext}" + And I call "{theChannel}" with "getCurrentContext" + Then "{result}" is an object with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.context.type | payload.context.id.ticker | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | one | fdc3.instrument | AAPL | broadcastRequest | + | one | {null} | {null} | getCurrentContextRequest | + + Scenario: Asking for a piece of context (e.g. an email) when it's not been sent returns null + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And messaging receives "{instrumentMessageOne}" + And I call "{theChannel}" with "getCurrentContext" using argument "fdc3.email" + Then "{result}" is null + + Scenario: User Channel Updated By Desktop Agent Changes User Channel Context Listeners + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And I refer to "{result}" as "theListener" + When messaging receives "{userChannelMessage2}" + # Channel changed event handling is async + And we wait for a period of "100" ms + Then "{channelId}" is "two" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + + Scenario: User Channel Updated By Desktop Agent To A Non-Existent User Channel Sets The Channel To Null + When I call "{api}" with "joinUserChannel" using argument "one" + When messaging receives "{userChannelMessageBroken}" + # Channel changed event handling is async and this case involves an extra round trip to the DA + And we wait for a period of "500" ms + Then "{channelId}" is "{null}" + + Scenario: Adding and removing A User Channel Changed Event Listener + Given "typesHandler" pipes events to "types" + When I call "{api}" with "addEventListener" using arguments "userChannelChanged" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{userChannelMessage2}" + And messaging receives "{userChannelMessage1}" + And I call "{theListener}" with "unsubscribe" + And messaging receives "{userChannelMessage3}" + Then messaging will have posts + | payload.type | type | matches_type | + | USER_CHANNEL_CHANGED | addEventListenerRequest | addEventListenerRequest | + | USER_CHANNEL_CHANGED | addEventListenerRequest | addEventListenerRequest | + | {null} | getUserChannelsRequest | getUserChannelsRequest | + | {null} | getUserChannelsRequest | getUserChannelsRequest | + | {null} | eventListenerUnsubscribeRequest | eventListenerUnsubscribeRequest | + And "{types}" is an array of objects with the following contents + | currentChannelId | + | two | + | one | + + Scenario: Adding and removing A "null" (i.e. wildcard) Event Listener + Given "typesHandler" pipes events to "types" + When I call "{api}" with "addEventListener" using arguments "{null}" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{userChannelMessage2}" + And messaging receives "{userChannelMessage1}" + And I call "{theListener}" with "unsubscribe" + And messaging receives "{userChannelMessage3}" + Then "{types}" is an array of objects with the following contents + | currentChannelId | + | two | + | one | + And messaging will have posts + | payload.type | type | matches_type | + | USER_CHANNEL_CHANGED | addEventListenerRequest | addEventListenerRequest | + | {null} | addEventListenerRequest | addEventListenerRequest | + | {null} | getUserChannelsRequest | getUserChannelsRequest | + | {null} | getUserChannelsRequest | getUserChannelsRequest | + | {null} | eventListenerUnsubscribeRequest | eventListenerUnsubscribeRequest | + + Scenario: Adding An Unknown Event Listener + Given "typesHandler" pipes events to "types" + When I call "{api}" with "addEventListener" using arguments "unknownEventType" and "{typesHandler}" + Then "{result}" is an error with message "UnknownEventType" + + Scenario: User Channel Changed Event fires when currentChannelId field is used + Given "typesHandler" pipes events to "types" + And "modernMessage" is a channelChangedEvent message with currentChannelId "channelX" + When I call "{api}" with "addEventListener" using arguments "userChannelChanged" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{modernMessage}" + Then "{types}" is an array of objects with the following contents + | currentChannelId | + | channelX | + + Scenario: User Channel Changed Event fires when user leaves a channel via currentChannelId null + Given "typesHandler" pipes events to "types" + And "leaveMessage" is a channelChangedEvent message with currentChannelId "{null}" + When I call "{api}" with "addEventListener" using arguments "userChannelChanged" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{leaveMessage}" + Then "{types}" is an array of objects with the following contents + | currentChannelId | + | {null} | + + Scenario: User Channel Changed Event fires when user leaves a channel via deprecated newChannelId null + Given "typesHandler" pipes events to "types" + And "leaveMessageDeprecated" is a channelChangedEvent message on channel "{null}" + When I call "{api}" with "addEventListener" using arguments "userChannelChanged" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{leaveMessageDeprecated}" + Then "{types}" is an array of objects with the following contents + | currentChannelId | + | {null} | + + Scenario: currentChannelId takes precedence over deprecated newChannelId in channel changed events + Given "typesHandler" pipes events to "types" + And "bothFieldsMessage" is a channelChangedEvent message with currentChannelId "modern" and newChannelId "deprecated" + When I call "{api}" with "addEventListener" using arguments "userChannelChanged" and "{typesHandler}" + And I refer to "{result}" as "theListener" + And messaging receives "{bothFieldsMessage}" + Then "{types}" is an array of objects with the following contents + | currentChannelId | + | modern | + + Scenario: Wildcard event listener fires and forwards non-channelChangedEvent messages + Given "typesHandler" pipes events to "types" + When I call "{api}" with "addEventListener" using arguments "{null}" and "{typesHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{types}" is an array of objects with the following contents + | channelId | context.type | + | one | fdc3.instrument | + + Scenario: Destructured getUserChannels returns user channels + When I destructure method "getUserChannels" from "{api}" + And I call destructured "getUserChannels" + Then "{result}" is an array of objects with the following contents + | id | type | displayMetadata.color | displayMetadata.glyph | displayMetadata.name | + | one | user | red | triangle | The one channel | + | two | user | red | triangle | The two channel | + | three | user | red | triangle | The three channel | + + Scenario: Destructured joinUserChannel and getCurrentChannel work correctly + When I destructure method "joinUserChannel" from "{api}" + And I call destructured "joinUserChannel" using argument "one" + And I destructure method "getCurrentChannel" from "{api}" + And I call destructured "getCurrentChannel" + Then "{result}" is an object with the following contents + | id | type | displayMetadata.color | + | one | user | red | + And messaging will have posts + | payload.channelId | matches_type | + | one | joinUserChannelRequest | + | {null} | getUserChannelsRequest | + + Scenario: Destructured channel getCurrentContext after broadcast + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And I destructure methods "broadcast", "getCurrentContext" from "{api}" + And I destructure method "getCurrentContext" from "{theChannel}" + And I call destructured "broadcast" using argument "{instrumentContext}" + And I call destructured "getCurrentContext" + Then "{result}" is an object with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: Destructured broadcast on user channel + Given "resultHandler" pipes context to "contexts" + When I destructure method "broadcast" from "{api}" + And I call "{api}" with "joinUserChannel" using argument "one" + And I call destructured "broadcast" using argument "{instrumentContext}" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And I call "{theChannel}" with "getCurrentContext" + Then "{result}" is an object with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + + Scenario: Destructured user channel addContextListener works correctly + Given "resultHandler" pipes context to "contexts" + When I destructure method "addContextListener" from "{api}" + And I call "{api}" with "joinUserChannel" using argument "one" + And I call destructured "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + + Scenario: BroadcastEvent on app Opening + Given "resultHandler" pipes context to "contexts" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{openMessage}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And messaging will have posts + | payload.channelId | payload.contextType | matches_type | + | {null} | fdc3.instrument | addContextListenerRequest | + + Scenario: User channel context listener receives source metadata + Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" + When I call "{api}" with "joinUserChannel" using argument "one" + And I call "{api}" with "addContextListener" using arguments "fdc3.instrument" and "{resultHandler}" + And messaging receives "{instrumentMessageOne}" + Then "{contexts}" is an array of objects with the following contents + | id.ticker | type | name | + | AAPL | fdc3.instrument | Apple | + And "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | + | cucumber-app | cucumber-instance | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/utils.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/utils.feature new file mode 100644 index 00000000..6298470e --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/utils.feature @@ -0,0 +1,10 @@ +Feature: Utility functions + + Scenario: throwIfUndefined is used to check properties + When I call throwIfUndefined it throws if a specified property is not defined + And I call throwIfUndefined it does NOT throw if a specified property IS defined + + Scenario: Logger utility + When All log functions are used with a message + When All log functions are used with an error + \ No newline at end of file diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml new file mode 100644 index 00000000..0aaff42e --- /dev/null +++ b/fdc3-context/pom.xml @@ -0,0 +1,289 @@ + + + 4.0.0 + + org.finos.fdc3 + fdc3-parent + 1.0.0-SNAPSHOT + + + fdc3-context + FDC3 Context Types + Generated Java classes for FDC3 Context types from JSON Schema + + + UTF-8 + 11 + 11 + v20.11.0 + 10.2.4 + 2.2.3 + ${project.build.directory}/schemas-work + ${project.build.directory}/npm-work/node_modules/@finos/fdc3-context/dist/schemas/context + ${project.build.directory}/npm-work/node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json + install @finos/fdc3-context@${fdc3.context.version} @finos/fdc3-schema@${fdc3.context.version} quicktype --save + + + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.16.1 + + + + + org.junit.jupiter + junit-jupiter + 5.10.1 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + false + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + populate-schemas-work + generate-sources + + copy-resources + + + ${fdc3.context.schemas.work.dir}/context + true + + + ${fdc3.context.source.dir} + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + create-directories + initialize + + run + + + + + + + + + + + clean-generated-sources + generate-sources + + run + + + + + + + + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.15.0 + + ${node.version} + ${npm.version} + ${project.build.directory}/npm-work + ${project.build.directory}/node-installation + + + + + install-node-and-npm + generate-sources + + install-node-and-npm + + + + + npm-init + generate-sources + + npm + + + init -y + + + + + npm-install-packages + generate-sources + + npm + + + ${fdc3.npm.context.packages} + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.1 + + + quicktype-context + generate-sources + + exec + + + /bin/sh + ${project.build.directory}/npm-work + + -c + node_modules/.bin/quicktype -s schema --quiet --lang java --package org.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/context/ContextTypes.java -S ${fdc3.api.schema.path} $(find ${fdc3.context.source.dir} -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) + + + + + + fix-context-imports + generate-sources + + exec + + + sh + + ${project.basedir}/scripts/fix-context-imports.sh + ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/context + + + + + + verify-generated-classes + test-compile + + exec + + + sh + + -c + missing=0; for c in ActionType NothingType EmailRecipients ChartStyle; do \ + if [ ! -f "${project.build.directory}/classes/org/finos/fdc3/context/$c.class" ]; then \ + echo "Missing compiled class: org.finos.fdc3.context.$c"; missing=1; \ + fi; \ + done; exit $missing + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.3 + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/fdc3 + + + + + + + + + + + + download + + + + + local + + true + + + ${project.basedir}/src/schemas-temp/3.0.0/context + ${project.basedir}/../fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json + install quicktype --save + + + + diff --git a/fdc3-context/scripts/fix-context-imports.sh b/fdc3-context/scripts/fix-context-imports.sh new file mode 100644 index 00000000..5ef75bc6 --- /dev/null +++ b/fdc3-context/scripts/fix-context-imports.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Copyright FINOS FDC3 contributors - see NOTICE file +# +# Post-process quicktype Java output: remove duplicate Context types and fix imports. + +set -eu + +cd "${1:?usage: fix-context-imports.sh }" + +rm -f Context.java ContextElement.java 2>/dev/null || true + +for f in *.java; do + [ -f "$f" ] || continue + # Cross-platform in-place edit (GNU sed vs BSD sed) + if sed --version 2>/dev/null | grep -q GNU; then + sed -i -e 's/ContextElement/Context/g' "$f" + if grep -qE '(private|public) Context |Context get|Context\[\]|ContextFromJsonString' "$f"; then + sed -i '/^package org.finos.fdc3.context;$/a\ +import org.finos.fdc3.api.context.Context;' "$f" + fi + else + sed -i '' -e 's/ContextElement/Context/g' "$f" + if grep -qE '(private|public) Context |Context get|Context\[\]|ContextFromJsonString' "$f"; then + sed -i '' '/^package org.finos.fdc3.context;$/a\ +import org.finos.fdc3.api.context.Context;' "$f" + fi + fi +done diff --git a/fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java b/fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java new file mode 100644 index 00000000..a5e7423d --- /dev/null +++ b/fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java @@ -0,0 +1,152 @@ +package org.finos.fdc3.context; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.finos.fdc3.api.context.Context; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class for converting JSON to the appropriate FDC3 context type + * based on the "type" field in the JSON. + */ +public class ContextConverter { + + private static final ObjectMapper mapper = createObjectMapper(); + + private static ObjectMapper createObjectMapper() { + ObjectMapper om = new ObjectMapper(); + // Register Java 8 date/time module + om.registerModule(new JavaTimeModule()); + // Don't fail on unknown properties (FDC3 allows additional properties) + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // Write dates as ISO strings, not timestamps + om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + return om; + } + + // Map of FDC3 type strings to their corresponding Java classes + private static final Map> TYPE_MAP = new HashMap<>(); + + static { + TYPE_MAP.put("fdc3.action", Action.class); + TYPE_MAP.put("fdc3.chart", Chart.class); + TYPE_MAP.put("fdc3.chat.initSettings", ChatInitSettings.class); + TYPE_MAP.put("fdc3.chat.message", ChatMessage.class); + TYPE_MAP.put("fdc3.chat.room", ChatRoom.class); + TYPE_MAP.put("fdc3.chat.searchCriteria", ChatSearchCriteria.class); + TYPE_MAP.put("fdc3.contact", Contact.class); + TYPE_MAP.put("fdc3.contactList", ContactList.class); + TYPE_MAP.put("fdc3.context", Context.class); + TYPE_MAP.put("fdc3.country", Country.class); + TYPE_MAP.put("fdc3.currency", Currency.class); + TYPE_MAP.put("fdc3.email", Email.class); + TYPE_MAP.put("fdc3.fileAttachment", FileAttachment.class); + TYPE_MAP.put("fdc3.instrument", Instrument.class); + TYPE_MAP.put("fdc3.instrumentList", InstrumentList.class); + TYPE_MAP.put("fdc3.interaction", Interaction.class); + TYPE_MAP.put("fdc3.message", Message.class); + TYPE_MAP.put("fdc3.nothing", Nothing.class); + TYPE_MAP.put("fdc3.order", Order.class); + TYPE_MAP.put("fdc3.orderList", OrderList.class); + TYPE_MAP.put("fdc3.organization", Organization.class); + TYPE_MAP.put("fdc3.portfolio", Portfolio.class); + TYPE_MAP.put("fdc3.position", Position.class); + TYPE_MAP.put("fdc3.product", Product.class); + TYPE_MAP.put("fdc3.timeRange", TimeRange.class); + TYPE_MAP.put("fdc3.trade", Trade.class); + TYPE_MAP.put("fdc3.tradeList", TradeList.class); + TYPE_MAP.put("fdc3.transactionResult", TransactionResult.class); + TYPE_MAP.put("fdc3.valuation", Valuation.class); + } + + /** + * Get the Java class for a given FDC3 type string. + * + * @param fdc3Type the FDC3 type string (e.g., "fdc3.contact") + * @return the corresponding Java class, or null if not found + */ + public static Class getClassForType(String fdc3Type) { + return TYPE_MAP.get(fdc3Type); + } + + /** + * Parse a JSON string and return the appropriate context object based on the "type" field. + * + * @param json the JSON string to parse + * @return the parsed context object + * @throws IOException if parsing fails + * @throws IllegalArgumentException if the type is unknown + */ + public static Object fromJson(String json) throws IOException { + JsonNode node = mapper.readTree(json); + String type = node.has("type") ? node.get("type").asText() : null; + + if (type == null) { + throw new IllegalArgumentException("JSON does not contain a 'type' field"); + } + + Class clazz = TYPE_MAP.get(type); + if (clazz == null) { + throw new IllegalArgumentException("Unknown context type: " + type); + } + + return mapper.treeToValue(node, clazz); + } + + /** + * Parse a JSON string into a specific context class. + * + * @param json the JSON string to parse + * @param clazz the target class + * @param the type of the context + * @return the parsed context object + * @throws IOException if parsing fails + */ + public static T fromJson(String json, Class clazz) throws IOException { + return mapper.readValue(json, clazz); + } + + /** + * Serialize a context object to JSON. + * + * @param context the context object to serialize + * @return the JSON string + * @throws JsonProcessingException if serialization fails + */ + public static String toJson(Object context) throws JsonProcessingException { + return mapper.writeValueAsString(context); + } + + /** + * Serialize a context object to pretty-printed JSON. + * + * @param context the context object to serialize + * @return the pretty-printed JSON string + * @throws JsonProcessingException if serialization fails + */ + public static String toJsonPretty(Object context) throws JsonProcessingException { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(context); + } + + /** + * Check if two JSON strings are semantically equivalent (same content, possibly different formatting). + * + * @param json1 first JSON string + * @param json2 second JSON string + * @return true if the JSON objects are equivalent + * @throws IOException if parsing fails + */ + public static boolean jsonEquals(String json1, String json2) throws IOException { + JsonNode node1 = mapper.readTree(json1); + JsonNode node2 = mapper.readTree(json2); + return node1.equals(node2); + } +} + diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/action.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/action.schema.json new file mode 100644 index 00000000..4249ba88 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/action.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/action.schema.json", + "title": "Action", + "description": "A representation of an FDC3 Action (specified via a Context or Context & Intent) that can be inserted inside another object, for example a chat message.\n\nThe action may be completed by calling:\n- `fdc3.raiseIntent()` with the specified Intent and Context\n- `fdc3.raiseIntentForContext()` if only a context is specified, (which the Desktop Agent will resolve by presenting the user with a list of available Intents for the Context).\n- `channel.broadcast()` with the specified Context, if the `broadcast` action has been defined.\n\nAccepts an optional `app` parameter in order to specify a specific app.", + "allOf": [{ + "type": "object", + "properties": { + "type": { "const": "fdc3.action" }, + "action": { + "title": "Action Type", + "description": "The **action** field indicates the type of action:\n- **raiseIntent** : If no action or `raiseIntent` is specified, then `fdc3.raiseIntent` or `fdc3.raiseIntentForContext` will be called with the specified context (and intent if given).\n- **broadcast** : If `broadcast` and a `channelId` are specified then `fdc3.getOrCreateChannel(channelId)` is called to retrieve the channel and broadcast the context to it with `channel.broadcast(context)`. If no `channelId` has been specified, the context should be broadcast to the current channel (`fdc3.broadcast()`)", + "type": "string", + "enum": [ + "broadcast", + "raiseIntent" + ] + }, + "title": { + "title": "Action Title", + "description": "A human readable display name for the action", + "type": "string" + }, + "intent": { + "title": "Action Intent", + "description": "Optional Intent to raise to perform the actions. Should reference an intent type name, such as those defined in the FDC3 Standard. If intent is not set then `fdc3.raiseIntentForContext` should be used to perform the action as this will usually allow the user to choose the intent to raise.", + "type": "string" + }, + "context": { + "title": "Action Context", + "description": "A context object with which the action will be performed", + "$ref": "context.schema.json#" + }, + "channelId": { + "title": "Channel ID", + "description": "Optional channel on which to broadcast the context. The `channelId` property is ignored unless the `action` is broadcast.", + "type": "string" + }, + "app": { + "title": "Action Target App", + "description": "An optional target application identifier that should perform the action. The `app` property is ignored unless the action is raiseIntent.", + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + } + }, + "required": [ + "title", "context" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.action", + "action": "raiseIntent", + "title": "Click to view Chart", + "intent": "ViewChart", + "context": { + "type": "fdc3.chart", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "EURUSD" + } + } + ], + "range": { + "type": "fdc3.dateRange", + "starttime": "2020-09-01T08:00:00.000Z", + "endtime": "2020-10-31T08:00:00.000Z" + }, + "style": "candle" + }, + "app" :{ + "appId": "MyChartViewingApp", + "instanceId": "instance1" + } + }, + { + "type": "fdc3.action", + "action": "broadcast", + "channelId": "Channel 1", + "title": "Click to view Chart", + "context": { + "type": "fdc3.chart", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "EURUSD" + } + } + ], + "range": { + "type": "fdc3.dateRange", + "starttime": "2020-09-01T08:00:00.000Z", + "endtime": "2020-10-31T08:00:00.000Z" + }, + "style": "candle" + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/chart.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/chart.schema.json new file mode 100644 index 00000000..af37550c --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/chart.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/chart.schema.json", + "type": "object", + "title": "Chart", + "description": "A context type representing details of a Chart, which may be used to request plotting of a particular chart or to otherwise share details of its composition, such as:\n\n- A list of instruments for comparison\n- The time period to plot the chart over\n- The style of chart (line, bar, mountain, candle etc.)\n- Other settings such as indicators to calculate, or data representing drawings and annotations.\n\nIn addition to handling requests to plot charts, a charting application may use this type to output a representation of what it is currently displaying so that it can be recorded by another application.", + "allOf": [{ + "type": "object", + "properties": { + "type": { "const": "fdc3.chart" }, + "instruments": { + "title": "Instruments to plot", + "description": "An array of instrument contexts whose data should be plotted.", + "type": "array", + "items": { + "$ref": "instrument.schema.json#" + } + }, + "range": { + "title": "Time Range", + "description": "The time range that should be plotted", + "$ref": "timeRange.schema.json#" + }, + "style": { + "title": "Chart style", + "description": "The type of chart that should be plotted", + "type": "string", + "enum": [ "line", "bar", "stacked-bar", "mountain", "candle", "pie", "scatter", "histogram", "heatmap", "custom"] + }, + "otherConfig": { + "title": "Other configuration", + "description": "It is common for charts to support other configuration, such as indicators, annotations etc., which do not have standardized formats, but may be included in the `otherConfig` array as context objects.", + "type": "array", + "items": { + "$ref": "context.schema.json#" + } + } + }, + "required": ["instruments"] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.chart", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "AAPL" + } + }, + { + "type": "fdc3.instrument", + "id": { + "ticker": "GOOG" + } + } + ], + "range": { + "type": "fdc3.timeRange", + "startTime": "2020-09-01T08:00:00.000Z", + "endTime": "2020-10-31T08:00:00.000Z" + }, + "style": "line", + "otherConfig": [ + { + "type": "somevendor.someproduct.indicator", + "name": "stddev", + "parameters": { + "period": 10, + "matype": "exponential" + } + }, + { + "type": "someothervendor.someotherproduct.formula", + "formula": "standard-deviation", + "fields": { + "lookback": 10, + "type": "ema" + } + } + ] + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/chatInitSettings.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/chatInitSettings.schema.json new file mode 100644 index 00000000..90220988 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/chatInitSettings.schema.json @@ -0,0 +1,115 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/chatInitSettings.schema.json", + "type": "object", + "title": "ChatInitSettings", + "description": "A collection of settings to start a new chat conversation", + "allOf": [{ + "type": "object", + "properties": { + "type": { "const": "fdc3.chat.initSettings" }, + "chatName": { + "title": "Chat name", + "description": "Name to apply to the chat created", + "type": "string" + }, + "members": { + "title": "Chat members", + "description": "Contacts to add to the chat", + "$ref": "contactList.schema.json#" + }, + "message": { + "title": "Initial chat message", + "description": "An initial message to post in the chat when created.", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "message.schema.json#" + } + ] + }, + "options": { + "title": "Chat options", + "description": "Option settings that affect the creation of the chat", + "type": "object", + "properties": { + "groupRecipients": { + "title": "Group recipients option", + "description": "if false a separate chat will be created for each member", + "type": "boolean" + }, + "isPublic": { + "title": "Public chat option", + "description": "if true the room will be visible to everyone in the chat application", + "type": "boolean" + }, + "allowHistoryBrowsing": { + "title": "Allow history browsing option", + "description": "if true members will be allowed to browse past messages", + "type": "boolean" + }, + "allowMessageCopy": { + "title": "Allow message copy option", + "description": "if true members will be allowed to copy/paste messages", + "type": "boolean" + }, + "allowAddUser": { + "title": "All adding users option", + "description": "if true members will be allowed to add other members to the chat", + "type": "boolean" + } + } + } + } + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.chat.initSettings", + "chatName": "Chat ABCD", + "members": { + "type": "fdc3.contactList", + "contacts": [ + { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane@mail.com" + } + }, + { + "type": "fdc3.contact", + "name": "John Doe", + "id": { + "email": "john@mail.com" + } + } + ] + }, + "options": { + "groupRecipients": true, + "isPublic": false, + "allowHistoryBrowsing": true, + "allowMessageCopy": true + }, + "message": { + "type": "fdc3.message", + "text": { + "text/plain": "Hey all, can we discuss the issue together? I attached a screenshot" + }, + "entities": { + "0": { + "type": "fdc3.fileAttachment", + "data": { + "name": "myImage.png", + "dataUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" + } + } + } + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/chatMessage.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/chatMessage.schema.json new file mode 100644 index 00000000..a60e306a --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/chatMessage.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/chatMessage.schema.json", + "type": "object", + "title": "ChatMessage", + "description": "A context representing a chat message. Typically used to send the message or to pre-populate a message for sending.", + "allOf": [{ + "type": "object", + "properties": { + "type": { "const": "fdc3.chat.message" }, + "chatRoom": { "$ref": "chatRoom.schema.json#" }, + "message": { "$ref": "message.schema.json#" } + }, + "required": ["type", "chatRoom", "message"] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [{ + "type": "fdc3.chat.message", + "chatRoom": { + "type": "fdc3.chat.room", + "providerName": "Symphony", + "id": { + "streamId": "j75xqXy25NBOdacUI3FNBH" + } + }, + "message": { + "type": "fdc3.message", + "text": { + "text/plain": "Hey all, can we discuss the issue together? I attached a screenshot" + }, + "entities": { + "0": { + "type": "fdc3.fileAttachment", + "data": { + "name": "myImage.png", + "dataUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" + } + } + } + } + }] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/chatRoom.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/chatRoom.schema.json new file mode 100644 index 00000000..8033b976 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/chatRoom.schema.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/chatRoom.schema.json", + "type": "object", + "title": "ChatRoom", + "description": "Reference to the chat room which could be used to send a message to the room", + "allOf": [{ + "type": "object", + "properties": { + "type": { "const": "fdc3.chat.room" }, + "providerName": { + "title": "Chat provider name", + "description": "The name of the service that hosts the chat", + "type": "string" + }, + "id": { + "title": "Chat room id", + "description": "Identifier(s) for the chat - currently unstandardized", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "url": { + "title": "Chat URL", + "description": "Universal url to access to the room. It could be opened from a browser, a mobile app, etc...", + "type": "string", + "format": "uri" + }, + "name": { + "title": "Chat name", + "description": "Display name for the chat room", + "type": "string" + } + }, + "required": ["providerName", "id"] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.chat.room", + "providerName": "Symphony", + "id": { + "streamId": "j75xqXy25NBOdacUI3FNBH" + }, + "url": "http://symphony.com/ref/room/j75xqXy25NBOdacUI3FNBH___pqSsuJRdA", + "name": "My new room" + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/chatSearchCriteria.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/chatSearchCriteria.schema.json new file mode 100644 index 00000000..1318ee4c --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/chatSearchCriteria.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/chatSearchCriteria.schema.json", + "type": "object", + "title": "ChatSearchCriteria", + "description": "A context type that represents a simple search criterion, based on a list of other context objects, that can be used to search or filter messages in a chat application.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.chat.searchCriteria" + }, + "criteria": { + "title": "Search Criteria array", + "description": "An array of criteria that should match chats returned from by a search.\n\n⚠️ Operators (and/or/not) are not defined in `fdc3.chat.searchCriteria`. It is up to the application that processes the FDC3 Intent to choose and apply the operators between the criteria.\n\nEmpty search criteria can be supported to allow resetting of filters.", + "type": "array", + "items": { + "$ref": "#/$defs/SearchCriteria" + } + } + }, + "required": [ + "criteria" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "$defs": { + "SearchCriteria": { + "title": "Search Criteria", + "description": "An individual criteria against which to match chat messages, based on an FDC3 context or free-text string.", + "oneOf": [ + { + "$ref": "instrument.schema.json#" + }, + { + "$ref": "organization.schema.json#" + }, + { + "$ref": "contact.schema.json#" + }, + { + "type": "string", + "title": "Free text", + "description": "Free text to be used for a keyword search" + } + ] + } + }, + "examples": [ + { + "type": "fdc3.chat.searchCriteria", + "criteria": [ + { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + }, + { + "type": "fdc3.instrument", + "id": { + "ticker": "TSLA" + }, + "name": "Tesla, inc." + }, + "annual return" + ] + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/contact.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/contact.schema.json new file mode 100644 index 00000000..41b007e0 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/contact.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/contact.schema.json", + "type": "object", + "title": "Contact", + "description": "A person contact that can be engaged with through email, calling, messaging, CMS, etc.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.contact" + }, + "id": { + "type": "object", + "title": "Contact Identifiers", + "description": "Identifiers that relate to the Contact represented by this context", + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "Email address", + "description": "The email address for the contact" + }, + "FDS_ID": { + "type": "string", + "title": "FDS ID", + "description": "FactSet Permanent Identifier representing the contact" + } + } + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the contact" + } + }, + "required": [ + "id" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/contactList.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/contactList.schema.json new file mode 100644 index 00000000..3c501735 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/contactList.schema.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/contactList.schema.json", + "type": "object", + "title": "ContactList", + "description": "A collection of contacts, e.g. for chatting to or calling multiple contacts.\n\nThe contact list schema does not explicitly include identifiers in the `id` section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.contactList" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Contact List Identifiers", + "description": "One or more identifiers that refer to the contact list in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable summary of the contact list" + }, + "contacts": { + "type": "array", + "title": "List of Contacts", + "description": "An array of contact contexts that forms the list.", + "items": { + "$ref": "contact.schema.json#" + } + } + }, + "required": [ + "contacts" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.contactList", + "contacts": [ + { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + }, + { + "type": "fdc3.contact", + "name": "John Doe", + "id": { + "email": "john.doe@mail.com" + } + } + ] + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/context.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/context.schema.json new file mode 100644 index 00000000..c67aadd5 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/context.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/context.schema.json", + "type": "object", + "title": "Context", + "description": "The `fdc3.context` type defines the basic contract or \"shape\" for all data exchanged by FDC3 operations. As such, it is not really meant to be used on its own, but is imported by more specific type definitions (standardized or custom) to provide the structure and properties shared by all FDC3 context data types.\n\nThe key element of FDC3 context types is their mandatory `type` property, which is used to identify what type of data the object represents, and what shape it has.\n\nThe FDC3 context type, and all derived types, define the minimum set of fields a context data object of a particular type can be expected to have, but this can always be extended with custom fields as appropriate.", + "allOf": [ + { + "$ref": "#/definitions/DocumentedContext" + }, + { + "$ref": "#/definitions/BaseContext" + } + ], + "definitions": { + "BaseContext": { + "$comment": "Base definition for the Context object WITHOUT documentation (which will be imported into all derived types unless separated).", + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "object", + "unevaluatedProperties": { + "type": "string" + } + } + }, + "additionalProperties": true, + "required": [ + "type" + ] + }, + "DocumentedContext": { + "$comment": "Base definition for the Context object WITH documentation (which will be imported into all derived types unless separated).", + "type": "object", + "properties": { + "type": { + "type": "string", + "title": "Type", + "description": "The type property is the only _required_ part of the FDC3 context data schema. The FDC3 [API](https://fdc3.finos.org/docs/api/spec) relies on the `type` property being present to route shared context data appropriately.\n\nFDC3 [Intents](https://fdc3.finos.org/docs/intents/spec) also register the context data types they support in an FDC3 [App Directory](https://fdc3.finos.org/docs/app-directory/overview), used for intent discovery and routing.\n\nStandardized FDC3 context types have well-known `type` properties prefixed with the `fdc3` namespace, e.g. `fdc3.instrument`. For non-standard types, e.g. those defined and used by a particular organization, the convention is to prefix them with an organization-specific namespace, e.g. `blackrock.fund`.\n\nSee the [Context Data Specification](https://fdc3.finos.org/docs/context/spec) for more information about context data types." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Context data objects may include a name property that can be used for more information, or display purposes. Some derived types may require the name object as mandatory, depending on use case." + }, + "id": { + "type": "object", + "title": "Id", + "description": "Context data objects may include a set of equivalent key-value pairs that can be used to help applications identify and look up the context type they receive in their own domain. The idea behind this design is that applications can provide as many equivalent identifiers to a target application as possible, e.g. an instrument may be represented by an ISIN, CUSIP or Bloomberg identifier.\n\nIdentifiers do not make sense for all types of data, so the `id` property is therefore optional, but some derived types may choose to require at least one identifier. Identifier values SHOULD always be of type string.", + "unevaluatedProperties": { + "type": "string" + } + } + }, + "additionalProperties": true, + "required": [ + "type" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/country.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/country.schema.json new file mode 100644 index 00000000..8aaf6f24 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/country.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/country.schema.json", + "type": "object", + "title": "Country", + "description": "A country entity.\n\nNotes:\n\n- It is valid to include extra properties and metadata as part of the country payload, but the minimum requirement is for at least one standardized identifier to be provided\n\n - `COUNTRY_ISOALPHA2` SHOULD be preferred.\n\n- Try to only use country identifiers as intended and specified in the [ISO standard](https://en.wikipedia.org/wiki/ISO_3166-1). E.g. the `COUNTRY_ISOALPHA2` property must be a recognized value and not a proprietary two-letter code. If the identifier you want to share is not a standardized and recognized one, rather define a property that makes it clear what value it is. This makes it easier for target applications.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.country" + }, + "id": { + "type": "object", + "properties": { + "COUNTRY_ISOALPHA2": { + "type": "string", + "title": "COUNTRY_ISOALPHA2", + "description": "Two-letter ISO country code" + }, + "COUNTRY_ISOALPHA3": { + "type": "string", + "title": "COUNTRY_ISOALPHA3", + "description": "Three-letter ISO country code" + }, + "ISOALPHA2": { + "type": "string", + "title": "ISOALPHA2", + "description": "Two-letter ISO country code. Deprecated in FDC3 2.0 in favour of the version prefixed with `COUNTRY_`.", + "deprecated": true + }, + "ISOALPHA3": { + "type": "string", + "title": "ISOALPHA3", + "description": "Three-letter ISO country code. Deprecated in FDC3 2.0 in favour of the version prefixed with `COUNTRY_`.", + "deprecated": true + } + } + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the country" + } + }, + "required": [ + "id" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.country", + "name": "Sweden", + "id": { + "COUNTRY_ISOALPHA2": "SE" + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/currency.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/currency.schema.json new file mode 100644 index 00000000..a58b5b75 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/currency.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/currency.schema.json", + "type": "object", + "title": "Currency", + "description": "A context representing an individual Currency.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.currency" + }, + "name": { + "type": "string", + "title": "Currency name", + "description": "The name of the currency for display purposes" + }, + "id": { + "type": "object", + "properties": { + "CURRENCY_ISOCODE": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "title": "CURRENCY_ISOCODE", + "description": "The `CURRENCY_ISOCODE` should conform to 3 character alphabetic codes defined in [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html)" + } + } + } + }, + "required": [ + "id" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.currency", + "name": "US Dollar", + "id": { + "CURRENCY_ISOCODE": "USD" + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/email.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/email.schema.json new file mode 100644 index 00000000..a54e0b8d --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/email.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/email.schema.json", + "type": "object", + "title": "Email", + "description": "A collection of information to be used to initiate an email with a Contact or ContactList.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.email" + }, + "recipients": { + "title": "Email Recipients", + "description": "One or more recipients for the email.", + "oneOf": [ + { + "$ref": "contact.schema.json#" + }, + { + "$ref": "contactList.schema.json#" + } + ] + }, + "subject": { + "title": "Email Subject", + "description": "Subject line for the email.", + "type": "string" + }, + "textBody": { + "title": "Email Body", + "description": "Body content for the email.", + "type": "string" + } + }, + "required": [ + "recipients" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.email", + "recipients": { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@example.com" + } + }, + "subject": "The information you requested", + "textBody": "Blah, blah, blah ..." + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/fileAttachment.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/fileAttachment.schema.json new file mode 100644 index 00000000..b049af8c --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/fileAttachment.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/fileAttachment.schema.json", + "type": "object", + "title": "File Attachment", + "description": "A File attachment encoded in the form of a data URI. Can be added to a Message.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.fileAttachment" + }, + "data": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "File name", + "description": "The name of the attached file" + }, + "dataUri": { + "type": "string", + "format": "uri", + "title": "", + "description": "A data URI encoding the content of the file to be attached" + } + }, + "required": [ + "name", + "dataUri" + ] + } + }, + "required": [ + "type", + "data" + ] + }, + { + "$ref": "context.schema.json#/definitions/BaseContext" + } + ], + "examples": [ + { + "type": "fdc3.fileAttachment", + "data": { + "name": "myImage.png", + "dataUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/instrument.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/instrument.schema.json new file mode 100644 index 00000000..81fa02ab --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/instrument.schema.json @@ -0,0 +1,170 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/instrument.schema.json", + "type": "object", + "title": "Instrument", + "description": "A financial instrument from any asset class.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.instrument" + }, + "id": { + "title": "Instrument identifiers", + "description": "Any combination of instrument identifiers can be used together to resolve ambiguity, or for a better match. Not all applications will use the same instrument identifiers, which is why FDC3 allows for multiple to be specified. In general, the more identifiers an application can provide, the easier it will be to achieve interoperability.\n\nIt is valid to include extra properties and metadata as part of the instrument payload, but the minimum requirement is for at least one instrument identifier to be provided.\n\nTry to only use instrument identifiers as intended. E.g. the `ticker` property is meant for tickers as used by an exchange.\nIf the identifier you want to share is not a ticker or one of the other standardized fields, define a property that makes it clear what the value represents. Doing so will make interpretation easier for the developers of target applications.", + "type": "object", + "properties": { + "BBG": { + "type": "string", + "title": "Bloomberg security", + "description": "https://www.bloomberg.com/" + }, + "CUSIP": { + "type": "string", + "title": "CUSIP", + "description": "https://www.cusip.com/" + }, + "FDS_ID": { + "type": "string", + "title": "FactSet Permanent Security Identifier", + "description": "https://www.factset.com/" + }, + "FIGI": { + "type": "string", + "title": "Open FIGI", + "description": "https://www.openfigi.com/" + }, + "ISIN": { + "type": "string", + "title": "ISIN", + "description": "https://www.isin.org/" + }, + "PERMID": { + "type": "string", + "title": "Refinitiv PERMID", + "description": "https://permid.org/" + }, + "RIC": { + "type": "string", + "title": "Refinitiv Identification Code", + "description": "https://www.refinitiv.com/" + }, + "SEDOL": { + "type": "string", + "title": "SEDOL", + "description": "https://www.lseg.com/sedol" + }, + "ticker": { + "type": "string", + "title": "Stock ticker", + "description": "Unstandardized stock tickers" + } + } + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the instrument" + }, + "market": { + "description": "The `market` map can be used to further specify the instrument and help achieve interoperability between disparate data sources. This is especially useful when using an `id` field that is not globally unique.", + "type": "object", + "properties": { + "MIC": { + "type": "string", + "title": "Market Identifier Code", + "description": "https://en.wikipedia.org/wiki/Market_Identifier_Code" + }, + "name": { + "type": "string", + "title": "Market Name", + "description": "Human readable market name" + }, + "COUNTRY_ISOALPHA2": { + "type": "string", + "title": "Country ISO Code", + "description": "https://www.iso.org/iso-3166-country-codes.html" + }, + "BBG": { + "type": "string", + "title": "Bloomberg Market Identifier", + "description": "https://www.bloomberg.com/" + } + }, + "unevaluatedProperties": { + "type": "string" + } + }, + "classification": { + "title": "Instrument Classification", + "description": "@experimental The `classification` map can be used to specify the categorization of the instrument and help achieve interoperability between disparate data sources.", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "FDC3 Instrument Classification Name", + "description": "Optional human-readable classification, to be used if no specific data classification is available." + }, + "FDS_TYPE": { + "title": "FactSet Type", + "description": "FactSet classification for the instrument.", + "type": "string", + "enum": [ + "commodity", + "commodityIndex", + "corporateDebt", + "creditDefaultSwapIndex", + "deal", + "debt", + "debtIndex", + "etf", + "fixedIncome", + "future", + "governmentBenchmarkDebt", + "loan", + "mortgageBackedSecurity", + "municipalDebt", + "mutualFund", + "mutualFundIndex", + "option", + "otherDebt", + "ownershipPrivateCompany", + "pevcFirm", + "pevcFund", + "privateCompany", + "publicCompany", + "publicCompanyIndex", + "sovereignDebt", + "structuredProduct", + "unknown" + ] + } + } + } + }, + "required": [ + "type","id" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.instrument", + "name": "Microsoft", + "id": { + "ticker": "MSFT", + "RIC": "MSFT.OQ", + "ISIN": "US5949181045" + }, + "market": { + "MIC": "XNAS" + }, + "classification": { + "name": "publicCompany" + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/instrumentList.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/instrumentList.schema.json new file mode 100644 index 00000000..6f15daa9 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/instrumentList.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/instrumentList.schema.json", + "type": "object", + "title": "InstrumentList", + "description": "A collection of instruments. Use this type for use cases that require not just a single instrument, but multiple (e.g. to populate a watchlist). However, when holding information for each instrument is required, it is recommended to use the [Portfolio](Portfolio) type.\n\nThe instrument list schema does not explicitly include identifiers in the `id` section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.instrumentList" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Instrument List Identifiers", + "description": "One or more identifiers that refer to the instrument list in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable summary of the instrument list" + }, + "instruments": { + "type": "array", + "title": "List of instruments", + "description": "An array of instrument contexts that forms the list.", + "items": { + "$ref": "instrument.schema.json#" + } + } + }, + "required": [ + "instruments" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.instrumentList", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "AAPL" + }, + "market": { + "MIC": "XNAS" + } + }, + { + "type": "fdc3.instrument", + "id": { + "ISIN": "US5949181045" + } + } + ] + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/interaction.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/interaction.schema.json new file mode 100644 index 00000000..a07616f2 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/interaction.schema.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/interaction.schema.json", + "type": "object", + "title": "Interaction", + "description": "An `Interaction` is a significant direct exchange of ideas or information between a number of participants, e.g. a Sell Side party and one or more Buy Side parties. An `Interaction` might be a call, a meeting (physical or virtual), an IM or the preparation of some specialist data, such as financial data for a given company or sector.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.interaction" + }, + "id": { + "type": "object", + "title": "Interaction Id", + "description": "Can be used by a target application to pass an identifier back to the originating application after an interaction record has been created, updated or deleted. An interaction ID does not need to be populated by the originating application, however the target application could store it for future reference and SHOULD return it in a `TransactionResult`.", + "properties": { + "URI": { + "type": "string", + "title": "Interaction URI", + "description": "Can be used by a target application to pass a record's link back to the originating application. This offers the originating application a way to open the record for a user to view." + }, + "SALESFORCE": { + "type": "string", + "title": "Salesforce ID", + "description": "Interactions ID in Salesforce" + }, + "SINGLETRACK": { + "type": "string", + "title": "SingleTrack ID", + "description": "Interaction ID in SingleTrack" + } + } + }, + "participants": { + "title": "Interaction Participants", + "description": "A list of contacts involved in the interaction", + "$ref": "contactList.schema.json#" + }, + "timeRange": { + "title": "Interaction Time range", + "description": "The time range over which the interaction occurred", + "$ref": "timeRange.schema.json#" + }, + "interactionType": { + "title": "Interaction Type", + "description": "`interactionType` SHOULD be one of `'Instant Message'`, `'Email'`, `'Call'`, or `'Meeting'` although other string values are permitted.", + "type": "string" + }, + "description": { + "title": "Interaction Description", + "description": "A human-readable description of the interaction", + "type": "string" + }, + "initiator": { + "title": "Interaction Initiator", + "description": "The contact that initiated the interaction", + "$ref": "contact.schema.json#" + }, + "origin": { + "title": "Interaction Origin", + "description": "Used to represent the application or service that the interaction was created from to aid in tracing the source of an interaction.", + "type": "string" + } + }, + "required": [ + "participants", + "timeRange", + "interactionType", + "description" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.interaction", + "participants": { + "type": "fdc3.contactList", + "contacts": [ + { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + }, + { + "type": "fdc3.contact", + "name": "John Doe", + "id": { + "email": "john.doe@mail.com" + } + } + ] + }, + "interactionType": "Instant Message", + "timeRange": { + "type": "fdc3.timeRange", + "startTime": "2022-02-10T15:12:00Z" + }, + "description": "Laboris libero dapibus fames elit adipisicing eu, fermentum, dignissimos laboriosam, erat, risus qui deserunt. Praesentium! Reiciendis. Hic harum nostrud, harum potenti amet? Mauris. Pretium aliquid animi, eget eiusmod integer proident. Architecto ipsum blandit ducimus, possimus illum sunt illum necessitatibus ab litora sed, nonummy integer minus corrupti ducimus iste senectus accumsan, fugiat nostrud? Pede vero dictumst excepturi, iure earum consequuntur voluptatum", + "initiator": { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + }, + "origin": "Outlook" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/message.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/message.schema.json new file mode 100644 index 00000000..ac4b34ba --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/message.schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/message.schema.json", + "type": "object", + "title": "Message", + "description": "A chat message to be sent through an instant messaging application. Can contain one or several text bodies (organized by mime-type, plaintext or markdown), as well as attached entities (either arbitrary file attachments or FDC3 actions to be embedded in the message). To be put inside a ChatInitSettings object.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.message" + }, + "text": { + "type": "object", + "title": "Message text", + "description": "A map of string mime-type to string content", + "properties": { + "text/plain": { + "type": "string", + "title": "Plain text", + "description": "Plain text encoded content." + }, + "text/markdown": { + "title": "Markdown text", + "description": "Markdown encoded content", + "type": "string" + } + } + }, + "entities": { + "type": "object", + "title": "Message entities", + "description": "A map of string IDs to entities that should be attached to the message, such as an action to perform, a file attachment, or other FDC3 context object.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "action.schema.json#" + }, + { + "$ref": "fileAttachment.schema.json#" + } + ] + } + } + } + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.message", + "text": { + "text/plain": "Hey all, can we discuss the issue together? I attached a screenshot and a link to the current exchange rate" + }, + "entities": { + "picture1": { + "type": "fdc3.fileAttachment", + "data": { + "name": "myImage.png", + "dataUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII" + } + }, + "eurusd_action": { + "type": "fdc3.action", + "title": "Click to view Chart", + "intent": "ViewChart", + "context": { + "type": "fdc3.chart", + "instruments": [ + { + "type": "fdc3.instrument", + "id": { + "ticker": "EURUSD" + } + } + ], + "range": { + "type": "fdc3.dateRange", + "starttime": "2020-09-01T08:00:00.000Z", + "endtime": "2020-10-31T08:00:00.000Z" + }, + "style": "candle" + } + } + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/nothing.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/nothing.schema.json new file mode 100644 index 00000000..2b970c89 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/nothing.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/nothing.schema.json", + "type": "object", + "title": "Nothing", + "description": "A type that explicitly represents a lack of context.\n\nNotes:\n\n- Intended to be used in situations where no context is desired.\n- For example:\n - Raising an intent without context (e.g. opening a blank order form, or chat interface without a contact selected).\n - Resetting context on a channel (e.g. when context is used to set a filter in other applications a null context might release the filter).\n- An explicit representation of a Null or empty context allows apps to declare support for a lack of context, for example in their intent metadata in an app directory.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.nothing" + } + } + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.nothing" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/order.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/order.schema.json new file mode 100644 index 00000000..8aef9160 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/order.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/order.schema.json", + "type": "object", + "title": "Order", + "description": "@experimental context type representing an order. To be used with OMS and EMS systems.\n\nThis type currently only defines a required `id` field, which should provide a reference to the order in one or more systems, an optional human readable `name` field to be used to summarize the order and an optional `details` field that may be used to provide additional detail about the order, including a context representing a `product`, which may be extended with arbitrary properties. The `details.product` field is currently typed as a unspecified Context type, but both `details` and `details.product` are expected to be standardized in future.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.order" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Order Identifiers", + "description": "One or more identifiers that refer to the order in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable summary of the order." + }, + "details": { + "type": "object", + "title": "Order Details", + "description": "Optional additional details about the order, which may include a product element that is an, as yet undefined but extensible, Context", + "properties": { + "product": { + "$ref": "product.schema.json" + } + }, + "additionalProperties": true + }, + "notes": { + "type": "string", + "title": "Order Notes", + "description": "Additional notes or comments about the order." + } + }, + "required": [ + "type", + "id" + ], + "additionalProperties": true + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.order", + "name": "...", + "notes": "...", + "id": { + "myOMS": "12345" + }, + "details": { + "product": { + "type": "fdc3.product", + "id": { + "productId": "ABC123" + }, + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "MSFT" + } + } + } + } + }, + { + "type": "fdc3.order", + "id": { + "myOMS": "ABC123" + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/orderList.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/orderList.schema.json new file mode 100644 index 00000000..10ee4f78 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/orderList.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/orderList.schema.json", + "type": "object", + "title": "OrderList", + "description": "@experimental A list of orders. Use this type for use cases that require not just a single order, but multiple.\n\nThe OrderList schema does not explicitly include identifiers in the id section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.orderList" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Order List Identifiers", + "description": "One or more identifiers that refer to the order list in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable summary of the order list" + }, + "orders": { + "type": "array", + "items": { + "$ref": "order.schema.json#" + }, + "title": "List of Orders", + "description": "An array of order contexts that forms the list." + } + }, + "required": [ + "type", + "orders" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.orderList", + "orders": [ + { + "type": "fdc3.order", + "id": { + "myOMS": "ABC123" + } + }, + { + "type": "fdc3.order", + "id": { + "myOMS": "DEF456" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/organization.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/organization.schema.json new file mode 100644 index 00000000..b3e47110 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/organization.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/organization.schema.json", + "type": "object", + "title": "Organization", + "description": "An entity that can be used when referencing private companies and other organizations where a specific instrument is not available or desired e.g. CRM and News workflows.\n\nIt is valid to include extra properties and metadata as part of the organization payload, but the minimum requirement is for at least one specified identifier to be provided.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.organization" + }, + "id": { + "type": "object", + "title": "Organization Identifiers", + "description": "Identifiers for the organization, at least one must be provided.", + "minProperties": 1, + "properties": { + "LEI": { + "type": "string", + "title": "Legal Entity Identifier", + "description": "The Legal Entity Identifier (LEI) is a 20-character, alpha-numeric code based on the ISO 17442 standard developed by the International Organization for Standardization (ISO). It connects to key reference information that enables clear and unique identification of legal entities participating in financial transactions." + }, + "PERMID": { + "type": "string", + "title": "Organization", + "description": "Refinitiv Permanent Identifiers, or PermID for the organization" + }, + "FDS_ID": { + "type": "string", + "title": "Organization", + "description": "FactSet Permanent Identifier representing the organization" + } + } + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name of the organization" + } + }, + "required": [ + "id" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.organization", + "name": "Cargill, Incorporated", + "id": { + "LEI": "QXZYQNMR4JZ5RIRN4T31", + "FDS_ID": "00161G-E" + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/portfolio.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/portfolio.schema.json new file mode 100644 index 00000000..99feec9b --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/portfolio.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/portfolio.schema.json", + "type": "object", + "title": "Portfolio", + "description": "A financial portfolio made up of multiple positions (holdings) in several instruments. Contrast this with e.g. the [InstrumentList](InstrumentList) type, which is just a list of instruments.\n\nThis is a good example of how types can be composed and extended with extra properties to define more complex types.\n\nThe Portfolio type consists of an array of [Position](Position) types, each of which refers to a single [Instrument](Instrument) and a holding amount for that instrument.\n\nThe portfolio schema does not explicitly include identifiers in the `id` section, as there bis not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.portfolio" + }, + "positions": { + "type": "array", + "items": { + "$ref": "position.schema.json#" + }, + "title": "Portfolio positions", + "description": "The List of Positions which make up the Portfolio" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Portfolio Identifiers", + "description": "One or more identifiers that refer to the portfolio in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the portfolio" + } + }, + "required": [ + "positions" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.portfolio", + "positions": [ + { + "type": "fdc3.position", + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "AAPL" + } + }, + "holding": 2000000 + }, + { + "type": "fdc3.position", + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "MSFT" + } + }, + "holding": 1500000 + }, + { + "type": "fdc3.position", + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "IBM" + } + }, + "holding": 3000000 + } + ] + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/position.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/position.schema.json new file mode 100644 index 00000000..5f200639 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/position.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/position.schema.json", + "type": "object", + "title": "Position", + "description": "A financial position made up of an instrument and a holding in that instrument. This type is a good example of how new context types can be composed from existing types.\n\nIn this case, the instrument and the holding amount for that instrument are required values.\n\nThe [Position](Position) type goes hand-in-hand with the [Portfolio](Portfolio) type, which represents multiple holdings in a combination of instruments.\n\nThe position schema does not explicitly include identifiers in the `id` section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.position" + }, + "instrument": { + "$ref": "instrument.schema.json#", + "title": "The financial instrument that this position relates to", + "description": "" + }, + "holding": { + "type": "number", + "title": "The size of the holding represented by this position", + "description": "The amount of the holding, e.g. a number of shares" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Position Identifiers", + "description": "One or more identifiers that refer to the position in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the position" + } + }, + "required": [ + "instrument", + "holding" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.position", + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "AAPL" + } + }, + "holding": 2000000 + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/product.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/product.schema.json new file mode 100644 index 00000000..139fdaf3 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/product.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/product.schema.json", + "type": "object", + "title": "Product", + "description": "@experimental context type representing a tradable product. To be used with OMS and EMS systems.\n\nThis type is currently only loosely defined as an extensible context object, with an optional instrument field.\n\nThe Product schema does not explicitly include identifiers in the id section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.product" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Product Identifiers", + "description": "One or more identifiers that refer to the product. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Product Name", + "description": "A human-readable summary of the product." + }, + "instrument": { + "$ref": "instrument.schema.json", + "title": "Product Instrument", + "description": "A financial instrument that relates to the definition of this product" + }, + "notes": { + "type": "string", + "title": "Product Notes", + "description": "Additional notes or comments about the product." + } + }, + "required": [ + "type", + "id" + ], + "additionalProperties": true + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.product", + "notes": "...", + "id": { + "productId": "ABC123" + }, + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "MSFT" + } + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/security.encryptedContext.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/security.encryptedContext.schema.json new file mode 100644 index 00000000..3d71366e --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/security.encryptedContext.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/encrypted.schema.json", + "type": "object", + "title": "Encrypted Context Wrapper", + "description": "@experimental A wrapper context type for encrypted FDC3 context data. When an app broadcasts encrypted context data, the original type is preserved for routing purposes, while the remaining context information is encrypted. Recipients can request a symmetric key via 'fdc3.security.symmetricKey.request' to decrypt the payload.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.security.encryptedContext" + }, + "originalType": { + "type": "string", + "description": "The original FDC3 context type that was encrypted (e.g., 'fdc3.instrument', 'fdc3.contact'). This field is used by the desktop agent and context handlers for routing decisions." + }, + "id": { + "type": "object", + "description": "Identifiers for the encryption key used.", + "properties": { + "kid": { + "type": "string", + "description": "Key ID identifying the symmetric key used to encrypt the payload." + } + }, + "required": [ + "kid" + ], + "additionalProperties": true + }, + "encryptedPayload": { + "type": "string", + "description": "The encrypted context data as a base64-encoded string. Contains all fields from the original context except for the type. Encrypted using the symmetric key identified by 'id.kid'." + } + }, + "required": [ + "type", + "originalType", + "id", + "encryptedPayload" + ] + }, + { + "$ref": "context.schema.json#/definitions/BaseContext" + } + ], + "examples": [ + { + "type": "fdc3.security.encryptedContext", + "originalType": "fdc3.instrument", + "id": { + "kid": "channel-key-abc123" + }, + "encryptedPayload": "eyJuYW1lIjoiQXBwbGUiLCJpZCI6eyJ0aWNrZXIiOiJBQVBMIn19..." + }, + { + "type": "fdc3.security.encryptedContext", + "originalType": "fdc3.contact", + "id": { + "kid": "session-key-xyz789" + }, + "encryptedPayload": "eyJuYW1lIjoiSm9obiBEb2UiLCJpZCI6eyJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20ifX0=..." + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyRequest.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyRequest.schema.json new file mode 100644 index 00000000..349d30ef --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyRequest.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/symmetricKeyRequest.schema.json", + "type": "object", + "title": "Symmetric Key Request", + "description": "@experimental A request to obtain a symmetric encryption key for decrypting encrypted context on a channel.\n\n**Note:** This context type MUST be signed to be effective. The key owner uses the signature's public key URL to encrypt the symmetric key in the response, ensuring only the requesting application can decrypt it. See the [Security & Identity documentation](../../api/security) for details on signing context objects and encrypted communications.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.security.symmetricKeyRequest" + }, + "id": { + "type": "object", + "description": "Optional identifier for the requested key.", + "properties": { + "kid": { + "type": "string", + "description": "Key ID to request a specific symmetric key." + } + }, + "additionalProperties": true + } + }, + "required": [ + "type" + ] + }, + { + "$ref": "context.schema.json#/definitions/BaseContext" + } + ], + "examples": [ + { + "type": "fdc3.security.symmetricKeyRequest" + }, + { + "type": "fdc3.security.symmetricKeyRequest", + "id": { + "kid": "channel-key-abc123" + } + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyResponse.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyResponse.schema.json new file mode 100644 index 00000000..76be42ef --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyResponse.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/symmetricKeyResponse.schema.json", + "type": "object", + "title": "Symmetric Key Response", + "description": "@experimental A response containing a wrapped symmetric key and metadata.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.security.symmetricKeyResponse" + }, + "id": { + "type": "object", + "properties": { + "kid": { + "type": "string", + "description": "Key ID used to identify the public key used to wrap the symmetric key." + }, + "pki": { + "type": "string", + "description": "Public Key Infrastructure JSON Web Key Set URL used to wrap the symmetric key." + } + }, + "required": [ + "kid", + "pki" + ] + }, + "wrappedKey": { + "type": "string", + "description": "The symmetric key, encrypted using the recipient's public key.", + "examples": [ + "u4jvA7...==" + ] + } + }, + "required": [ + "type", + "id", + "wrappedKey" + ] + }, + { + "$ref": "context.schema.json#/definitions/BaseContext" + } + ], + "examples": [ + { + "type": "fdc3.security.symmetricKeyResponse", + "id": { + "kid": "key-id-123", + "pki": "https://examples.com/myJWKSendpoint" + }, + "wrappedKey": "u4jvA7Gx8LdH...==" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/security.user.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/security.user.schema.json new file mode 100644 index 00000000..de348e0c --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/security.user.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/user.schema.json", + "type": "object", + "title": "User", + "description": "@experimental A user identity, expressed as a wrapped JWT. Receivers will need to unwrap the JWT using their own private key.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.security.user" + }, + "wrappedJwt": { + "type": "string", + "description": "A JSON Web Token (JWT) asserting user identity and permissions, wrapped in the public key of the requester. The JWT contains a header with cryptographic information and a payload with user claims. Header fields include: 'alg' (signature algorithm, e.g., 'EdDSA'), 'jku' (JSON Web Key Set URL for key verification), and 'kid' (key identifier). Payload fields include: 'iss' (issuer - the application issuing the token), 'aud' (audience - the intended recipient application), 'sub' (subject - the user identifier), 'exp' (expiration time as Unix timestamp), 'iat' (issued at time as Unix timestamp), and 'jti' (JWT ID - unique token identifier)." + } + }, + "required": [ + "type", + "wrappedJwt" + ] + } + ], + "examples": [ + { + "type": "fdc3.security.user", + "wrappedJwt": "--example-jwt-token--but-wrapped-in-the-public-key-of-the-requester--" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/security.userRequest.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/security.userRequest.schema.json new file mode 100644 index 00000000..3f9adcf0 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/security.userRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/userRequest.schema.json", + "type": "object", + "title": "User Request", + "description": "@experimental A request for the current user's identity, typically raised via the CreateIdentityToken intent. An identity provider (IDP) receives this request and responds with an 'fdc3.user' context containing a signed JWT. The request includes cryptographic details needed for the IDP to create a token bound to the requesting application and to encrypt the response.\n\n**Note:** This context type MUST be signed to be effective. The IDP uses the signature's public key URL to verify the requesting application's identity and to encrypt the response. See the [Security & Identity documentation](../../api/security) for details on signing context objects.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.security.userRequest", + "description": "The FDC3 context type identifier. Used by desktop agents and context handlers to route this request to appropriate identity providers." + }, + "aud": { + "type": "string", + "format": "uri", + "description": "The audience identifier for the returned JWT, typically the URL of the requesting application. The identity provider will embed this value in the JWT's 'aud' claim, allowing the requesting application to verify that the token was issued specifically for it. This prevents token misuse if intercepted by other applications." + } + }, + "required": [ + "type", + "aud" + ], + "additionalProperties": true + }, + { + "$ref": "context.schema.json#/definitions/BaseContext" + } + ], + "examples": [ + { + "type": "fdc3.security.userRequest", + "aud": "https://my-app-url.com" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/timeRange.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/timeRange.schema.json new file mode 100644 index 00000000..7f3e15ca --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/timeRange.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/timeRange.schema.json", + "type": "object", + "title": "TimeRange", + "description": "A context representing a period of time. Any user interfaces that represent or visualize events or activity over time can be filtered or focused on a particular time period, e.g.:\n\n- A pricing chart\n- A trade blotter\n- A record of client contact/activity in a CRM\n\nExample use cases:\n\n- User may want to view pricing/trades/customer activity for a security over a particular time period, the time range might be specified as the context for the `ViewChart` intent OR it might be embedded in another context (e.g. a context representing a chart to plot).\n- User filters a visualization (e.g. a pricing chart) to show a particular period, the `TimeRange` is broadcast and other visualizations (e.g. a heatmap of activity by instrument, or industry sector etc.) receive it and filter themselves to show data over the same range.\n\nNotes:\n\n- A `TimeRange` may be closed (i.e. `startTime` and `endTime` are both known) or open (i.e. only one of `startTime` or `endTime` is known).\n- Ranges corresponding to dates (e.g. `2022-05-12` to `2022-05-19`) should be specified using times as this prevents issues with timezone conversions and inclusive/exclusive date ranges.\n- String fields representing times are encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html).\n - A timezone indicator should be specified, e.g. `\"2022-05-12T15:18:03Z\"` or `\"2022-05-12T16:18:03+01:00\"`\n - Times MAY be specified with millisecond precision, e.g. `\"2022-05-12T15:18:03.349Z\"`", + "allOf": [ + { + "properties": { + "type": { + "const": "fdc3.timeRange" + }, + "startTime": { + "type": "string", + "format": "date-time", + "title": "Start Time", + "description": "The start time of the range, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator." + }, + "endTime": { + "type": "string", + "format": "date-time", + "title": "End Time", + "description": "The end time of the range, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator." + } + } + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "anyOf": [ + { + "required": [ + "startTime", + "endTime" + ] + }, + { + "required": [ + "startTime" + ] + }, + { + "required": [ + "endTime" + ] + } + ], + "examples": [ + { + "type": "fdc3.timeRange", + "startTime": "2022-03-30T15:44:44Z", + "endTime": "2022-04-30T23:59:59Z" + }, + { + "type": "fdc3.timeRange", + "startTime": "2022-03-30T15:44:44+00:00" + }, + { + "type": "fdc3.timeRange", + "endTime": "2022-03-30T16:44:44.123Z" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/trade.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/trade.schema.json new file mode 100644 index 00000000..489b8b85 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/trade.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/trade.schema.json", + "type": "object", + "title": "Trade", + "description": "@experimental context type representing a trade. To be used with execution systems.\n\nThis type currently only defines a required `id` field, which should provide a reference to the trade in one or more systems, an optional human readable `name` field to be used to summarize the trade and a required `product` field that may be used to provide additional detail about the trade, which is currently typed as a unspecified Context type, but `product` is expected to be standardized in future.\n\n The Trade schema does not explicitly include identifiers in the id section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.trade" + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Trade Identifiers", + "description": "One or more identifiers that refer to the trade in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future." + }, + "name": { + "type": "string", + "title": "Trade Name", + "description": "A human-readable summary of the trade." + }, + "product": { + "$ref": "product.schema.json", + "title": "Traded product", + "description": "A product that is the subject of the trade." + }, + "notes": { + "type": "string", + "title": "Trade Notes", + "description": "Additional notes or comments about the trade." + } + }, + "required": [ + "type", "id", "product" + ], + "additionalProperties": true + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.trade", + "name": "...", + "notes": "...", + "id": { + "myEMS": "12345" + }, + "product": { + "type": "fdc3.product", + "id": { + "productId": "ABC123" + }, + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "MSFT" + } + } + } + } + ] +} diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/tradeList.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/tradeList.schema.json new file mode 100644 index 00000000..2cea41a8 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/tradeList.schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/tradeList.schema.json", + "type": "object", + "title": "TradeList", + "description": "@experimental A list of trades. Use this type for use cases that require not just a single trade, but multiple.\n\nThe TradeList schema does not explicitly include identifiers in the id section, as there is not a common standard for such identifiers. Applications can, however, populate this part of the contract with custom identifiers if so desired.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.tradeList" + }, + "trades": { + "title": "List of Trades", + "description": "An array of trade contexts that forms the list.", + "type": "array", + "items": { + "$ref": "trade.schema.json#" + } + }, + "id": { + "title": "Trade List Identifiers", + "description": "One or more identifiers that refer to the trade list in an OMS, EMS or related system. Specific key names for systems are expected to be standardized in future.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "type": "string", + "title": "Name", + "description": "An optional human-readable name for the trade list" + } + }, + "required": [ + "type", + "trades" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.tradeList", + "trades": [ + { + "type": "fdc3.trade", + "name": "...", + "id": { + "myEMS": "12345" + }, + "product": { + "type": "fdc3.product", + "id": { + "productId": "ABC123" + }, + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "MSFT" + } + } + } + }, + { + "type": "fdc3.trade", + "id": { + "myEMS": "67890" + }, + "product": { + "type": "fdc3.product", + "id": { + "productId": "DEF456" + }, + "instrument": { + "type": "fdc3.instrument", + "id": { + "ticker": "TSLA" + } + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/transactionresult.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/transactionresult.schema.json new file mode 100644 index 00000000..4bc0a6ff --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/transactionresult.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/transactionresult.schema.json", + "type": "object", + "title": "TransactionResult", + "description": "A context type representing the result of a transaction initiated via FDC3, which SHOULD be returned as an `IntentResult` by intents that create, retrieve, update or delete content or records in another application. Its purpose is to provide a status and message (where needed) for the transaction and MAY wrap a returned context object.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.transactionResult" + }, + "status": { + "type": "string", + "enum": [ + "Created", + "Deleted", + "Updated", + "Failed" + ], + "title": "Transaction Status", + "description": "The status of the transaction being reported." + }, + "context": { + "$ref": "context.schema.json#", + "title": "Transaction Result Context", + "description": "A context object returned by the transaction, possibly with updated data." + }, + "message": { + "type": "string", + "title": "Transaction Message", + "description": "A human readable message describing the outcome of the transaction." + } + }, + "required": [ + "type", + "status" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.transactionResult", + "status": "Updated", + "context": { + "type": "fdc3.contact", + "name": "Jane Doe", + "id": { + "email": "jane.doe@mail.com" + } + }, + "message": "record with id 'jane.doe@mail.com' was updated" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/schemas-temp/3.0.0/context/valuation.schema.json b/fdc3-context/src/schemas-temp/3.0.0/context/valuation.schema.json new file mode 100644 index 00000000..71276448 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/valuation.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/context/valuation.schema.json", + "type": "object", + "title": "Valuation", + "description": "A context type representing the price and value of a holding.", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "fdc3.valuation" + }, + "value": { + "type": "number", + "title": "Value", + "description": "The value of the holding, expresses in the nominated currency." + }, + "price": { + "type": "number", + "title": "Price per unit", + "description": "The price per unit the the valuation is based on." + }, + "CURRENCY_ISOCODE": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "title": "Valuation Currency", + "description": "The valuation currency, which should conform to 3 character alphabetic codes defined in [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html)" + }, + "valuationTime": { + "type": "string", + "format": "date-time", + "title": "Valuation time", + "description": "The time at which the valuation was performed, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator included." + }, + "expiryTime": { + "type": "string", + "format": "date-time", + "title": "Expiry Time", + "description": "The time at which this valuation expires, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator included." + } + }, + "required": [ + "value", + "CURRENCY_ISOCODE" + ] + }, + { "$ref": "context.schema.json#/definitions/BaseContext" } + ], + "examples": [ + { + "type": "fdc3.valuation", + "value": 500.0, + "price": 5.0, + "CURRENCY_ISOCODE": "USD", + "expiryTime": "2022-05-13T16:16:24+01:00" + } + ] +} \ No newline at end of file diff --git a/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java b/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java new file mode 100644 index 00000000..bf3eef78 --- /dev/null +++ b/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java @@ -0,0 +1,215 @@ +package org.finos.fdc3.context; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.OffsetDateTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that verify round-trip serialization of FDC3 context types. + * + * For each schema file: + * 1. Read the "examples" from the schema + * 2. Parse each example into the appropriate Java class + * 3. Re-serialize to JSON + * 4. Verify the re-serialized JSON matches the original + */ +public class ContextRoundTripTest { + + private static final ObjectMapper mapper = new ObjectMapper(); + private static Path schemasDir; + + @BeforeAll + static void setUp() { + String basePath = System.getProperty("user.dir"); + // Prefer unified schemas-work layout (used by both default and local-schemas profiles) + schemasDir = Paths.get(basePath, "target", "schemas-work", "context"); + + if (!Files.exists(schemasDir)) { + // Fallback for builds before prepare-schemas ran + schemasDir = Paths.get(basePath, "target", "npm-work", "node_modules", + "@finos", "fdc3-context", "dist", "schemas", "context"); + } + + if (!Files.exists(schemasDir)) { + // Try relative to project root + schemasDir = Paths.get("fdc3-context", "target", "schemas-work", "context"); + } + } + + @TestFactory + Collection testAllContextTypesRoundTrip() throws IOException { + List tests = new ArrayList<>(); + + if (!Files.exists(schemasDir)) { + System.err.println("Schemas directory not found: " + schemasDir); + System.err.println("Run 'mvn generate-sources' first to download the schemas."); + return tests; + } + + try (Stream paths = Files.list(schemasDir)) { + paths.filter(p -> p.toString().endsWith(".schema.json")) + .forEach(schemaPath -> { + String schemaName = schemaPath.getFileName().toString() + .replace(".schema.json", ""); + + try { + JsonNode schema = mapper.readTree(schemaPath.toFile()); + JsonNode examples = schema.get("examples"); + + if (examples != null && examples.isArray()) { + int exampleIndex = 0; + for (JsonNode example : examples) { + final int idx = exampleIndex++; + final String originalJson = mapper.writeValueAsString(example); + + tests.add(DynamicTest.dynamicTest( + schemaName + " - example " + idx, + () -> testRoundTrip(schemaName, originalJson) + )); + } + } + } catch (IOException e) { + tests.add(DynamicTest.dynamicTest( + schemaName + " - FAILED TO READ", + () -> fail("Failed to read schema: " + e.getMessage()) + )); + } + }); + } + + return tests; + } + + private void testRoundTrip(String schemaName, String originalJson) throws Exception { + // Get the type from the JSON + JsonNode node = mapper.readTree(originalJson); + String type = node.has("type") ? node.get("type").asText() : null; + + assertNotNull(type, "Example should have a 'type' field"); + + // Get the Java class for this type + Class clazz = ContextConverter.getClassForType(type); + + if (clazz == null) { + // Skip unknown types (like context.schema.json which is the base type) + System.out.println("Skipping unknown type: " + type + " from " + schemaName); + return; + } + + // Parse the JSON into the Java object + Object parsed; + try { + parsed = ContextConverter.fromJson(originalJson, clazz); + } catch (Exception e) { + // TODO: Check if this is a known issue with malformed example data + // fixed in current, unreleased FDC3. + if (e.getMessage() != null && e.getMessage().contains("23:59:59ZS")) { + System.out.println(" [KNOWN ISSUE] Skipping " + schemaName + + " example with malformed datetime (trailing 'S' in schema example)"); + return; + } + throw e; + } + assertNotNull(parsed, "Should be able to parse " + type); + + // Re-serialize to JSON + String reserialized = ContextConverter.toJson(parsed); + assertNotNull(reserialized, "Should be able to serialize " + type); + + // Parse both JSONs and compare (ignore formatting differences) + JsonNode originalNode = mapper.readTree(originalJson); + JsonNode reserializedNode = mapper.readTree(reserialized); + + // Check that all original fields are preserved + // Note: The re-serialized version might have fewer fields if they were null + assertJsonContains(originalNode, reserializedNode, schemaName + " (" + type + ")"); + } + + /** + * Asserts that all non-null fields in the original JSON are present in the reserialized JSON. + */ + private void assertJsonContains(JsonNode original, JsonNode reserialized, String context) { + original.fields().forEachRemaining(entry -> { + String fieldName = entry.getKey(); + JsonNode originalValue = entry.getValue(); + JsonNode reserializedValue = reserialized.get(fieldName); + + if (originalValue != null && !originalValue.isNull()) { + assertNotNull(reserializedValue, + "Field '" + fieldName + "' should be present in reserialized JSON for " + context); + + if (originalValue.isObject()) { + // Recursively check nested objects + assertJsonContains(originalValue, reserializedValue, context + "." + fieldName); + } else if (originalValue.isArray()) { + // Check array contents + assertEquals(originalValue.size(), reserializedValue.size(), + "Array '" + fieldName + "' should have same size for " + context); + } else if (originalValue.isNumber() && reserializedValue.isNumber()) { + // Compare numeric values with tolerance for integer/double differences + assertEquals(originalValue.doubleValue(), reserializedValue.doubleValue(), 0.0001, + "Field '" + fieldName + "' should have same numeric value for " + context); + } else if (originalValue.isTextual() && reserializedValue.isTextual()) { + // Compare string values, with special handling for date-times + String orig = originalValue.asText(); + String reser = reserializedValue.asText(); + if (!orig.equals(reser) && isDateTimeString(orig)) { + // Compare as date-times (handles +00:00 vs Z, .000Z vs Z, etc.) + assertTrue(dateTimesEqual(orig, reser), + "DateTime field '" + fieldName + "' should represent same instant for " + context + + " (original: " + orig + ", reserialized: " + reser + ")"); + } else { + assertEquals(orig, reser, + "Field '" + fieldName + "' should have same value for " + context); + } + } else { + // Compare primitive values + assertEquals(originalValue, reserializedValue, + "Field '" + fieldName + "' should have same value for " + context); + } + } + }); + } + + /** + * Check if a string looks like an ISO 8601 date-time. + */ + private boolean isDateTimeString(String s) { + return s != null && s.length() > 10 && s.contains("T") && + (s.endsWith("Z") || s.contains("+") || s.contains("-")); + } + + /** + * Compare two date-time strings, considering different representations of the same instant. + */ + private boolean dateTimesEqual(String dt1, String dt2) { + try { + // Normalize: remove trailing S if present (malformed data in some examples) + String s1 = dt1.replaceAll("S$", ""); + String s2 = dt2.replaceAll("S$", ""); + + OffsetDateTime odt1 = OffsetDateTime.parse(s1); + OffsetDateTime odt2 = OffsetDateTime.parse(s2); + return odt1.toInstant().equals(odt2.toInstant()); + } catch (DateTimeParseException e) { + // If we can't parse as date-time, fall back to string comparison + return dt1.equals(dt2); + } + } +} + diff --git a/fdc3-example-app/README.md b/fdc3-example-app/README.md new file mode 100644 index 00000000..da2760d1 --- /dev/null +++ b/fdc3-example-app/README.md @@ -0,0 +1,81 @@ +# FDC3 Example App + +A simple Java Swing application demonstrating FDC3 Desktop Agent connectivity via WebSocket. + +## Features + +- Connects to a Desktop Agent on startup using the Web Connection Protocol +- Displays available user channels and allows joining/leaving channels +- Shows a log of context broadcasts received on the current channel +- Supports adding and removing context listeners on the user channel + +## Prerequisites + +- Java 11 or higher +- A running FDC3 Desktop Agent (e.g., FDC3-Sail) + +## Building + +From the `fdc3-java-api` root directory: + +```bash +mvn clean package -pl fdc3-example-app -am +``` + +This will create an executable JAR with all dependencies at: +`fdc3-example-app/target/fdc3-example-app-1.0.0-SNAPSHOT.jar` + +## Running + +The application requires the following system properties to be set: + +| Property | Description | +| -------------------- | ----------------------------------- | +| `FDC3_WEBSOCKET_URL` | WebSocket URL for the Desktop Agent | + +### Example + +```bash +java -DFDC3_WEBSOCKET_URL=ws://localhost:4475/remote/my-app-path \ + -jar fdc3-example-app/target/fdc3-example-app-1.0.0-SNAPSHOT.jar +``` + +### Using with FDC3-Sail + +1. In FDC3-Sail, go to Configuration → Remote Apps +2. Add a new remote app by selecting an existing app from your directories +3. Copy the WebSocket URL provided +4. Use that URL and generate your own instance ID and UUID to run this application + +## UI Overview + +### Status Bar + +Shows the connection status to the Desktop Agent. + +### User Channel + +- **Current Channel**: Dropdown to select which user channel to join +- **Leave Channel**: Button to leave the current channel + +### Context Log + +Displays timestamped log entries for: + +- Connection events +- Channel join/leave events +- Context messages received on the current channel + +### Context Listener + +- **Add Listener**: Adds a context listener for all context types on the user channel +- **Remove Listener**: Removes the active context listener + +## Example Usage + +1. Start the application with the required system properties +2. Once connected, select a user channel from the dropdown +3. Click "Add Listener" to start receiving context broadcasts +4. Context received from other apps on the same channel will appear in the log +5. Use "Remove Listener" to stop receiving context +6. Use "Leave Channel" to leave the current channel diff --git a/fdc3-example-app/dependency-reduced-pom.xml b/fdc3-example-app/dependency-reduced-pom.xml new file mode 100644 index 00000000..bc16ef65 --- /dev/null +++ b/fdc3-example-app/dependency-reduced-pom.xml @@ -0,0 +1,58 @@ + + + + fdc3-parent + org.finos.fdc3 + 1.0.0-SNAPSHOT + + 4.0.0 + fdc3-example-app + FDC3 Example App + Example Java Swing application demonstrating FDC3 Desktop Agent connectivity + + + + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + + + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + org.finos.fdc3.example.ExampleApp + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + 11 + UTF-8 + 11 + + diff --git a/fdc3-example-app/pom.xml b/fdc3-example-app/pom.xml new file mode 100644 index 00000000..5ae4b78d --- /dev/null +++ b/fdc3-example-app/pom.xml @@ -0,0 +1,97 @@ + + + + 4.0.0 + + org.finos.fdc3 + fdc3-parent + 1.0.0-SNAPSHOT + + + fdc3-example-app + FDC3 Example App + Example Java Swing application demonstrating FDC3 Desktop Agent connectivity + + + UTF-8 + 11 + 11 + + + + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + + + + org.finos.fdc3 + fdc3-get-agent + ${project.version} + + + + + org.finos.fdc3 + fdc3-schema + ${project.version} + + + + + org.slf4j + slf4j-simple + 2.0.9 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + org.finos.fdc3.example.ExampleApp + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + diff --git a/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java b/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java new file mode 100644 index 00000000..80816b75 --- /dev/null +++ b/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java @@ -0,0 +1,808 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.example; + +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.getagent.GetAgent; +import org.finos.fdc3.getagent.GetAgentParams; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.GridLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Example Java Swing application demonstrating FDC3 Desktop Agent connectivity. + *

+ * This application: + *

    + *
  • Connects to a Desktop Agent via WebSocket on startup
  • + *
  • Displays the current user channel and allows changing channels
  • + *
  • Shows a log of context broadcasts received on the current channel
  • + *
  • Supports adding and removing context listeners
  • + *
+ *

+ * Required system properties or environment variables: + *

    + *
  • {@code FDC3_WEBSOCKET_URL} - WebSocket URL (e.g. ws://host/fdc3/ws)
  • + *
  • {@code FDC3_SESSION_ID} - Sail session ID from pairing UI
  • + *
  • {@code FDC3_CONNECTION_SECRET} - Per-app shared secret from pairing UI
  • + *
+ */ +public class ExampleApp extends JFrame { + + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + + private DesktopAgent agent; + private Listener contextListener; + private List userChannels; + private Channel currentChannel; + + // UI Components + private JComboBox channelComboBox; + private JTextArea logArea; + private JButton addListenerButton; + private JButton removeListenerButton; + private JLabel statusLabel; + private JLabel listenerStatusLabel; + + // Broadcast buttons + private JButton broadcastInstrumentButton; + private JButton broadcastCurrencyButton; + private JButton broadcastContactButton; + + private String websocketUrl; + private String sessionId; + private String sharedSecret; + + // Stored connection info for reconnection + private String lastInstanceId; + private String lastInstanceUuid; + + // Connection buttons + private JButton disconnectButton; + private JButton reconnectButton; + + public ExampleApp() { + super("FDC3 Example App"); + initUI(); + + // Check for WebSocket URL and prompt if needed + SwingUtilities.invokeLater(this::initializeConnection); + } + + /** + * Initialize the connection by checking for FDC3_WEBSOCKET_URL. + * If not set, prompt the user for the URL. + */ + private void initializeConnection() { + websocketUrl = envOrProperty("FDC3_WEBSOCKET_URL"); + sessionId = envOrProperty("FDC3_SESSION_ID"); + sharedSecret = envOrProperty("FDC3_CONNECTION_SECRET"); + + if (websocketUrl == null || sessionId == null || sharedSecret == null) { + promptForConnectionConfig(); + } else { + log("Using WSCP connection config from environment"); + connectToAgent(); + } + } + + private static String envOrProperty(String name) { + String v = System.getenv(name); + if (v == null || v.isEmpty()) { + v = System.getProperty(name); + } + return (v == null || v.isEmpty()) ? null : v; + } + + private void promptForConnectionConfig() { + String message = "WSCP connection values are required.\n\n" + + "Copy these from the Sail app directory for your native app:\n" + + " • WebSocket URL (same for all apps, e.g. ws://localhost:8090/fdc3/ws)\n" + + " • Session ID\n" + + " • Shared secret (unique per app)\n\n" + + "Enter WebSocket URL:"; + + String url = JOptionPane.showInputDialog(this, message, "WSCP WebSocket URL", + JOptionPane.QUESTION_MESSAGE); + if (url == null || url.trim().isEmpty()) { + showConfigError("No WebSocket URL provided."); + return; + } + websocketUrl = url.trim(); + + String sid = JOptionPane.showInputDialog(this, "Enter Session ID:", "WSCP Session ID", + JOptionPane.QUESTION_MESSAGE); + if (sid == null || sid.trim().isEmpty()) { + showConfigError("No session ID provided."); + return; + } + sessionId = sid.trim(); + + String secret = JOptionPane.showInputDialog(this, "Enter shared secret:", "WSCP Shared Secret", + JOptionPane.QUESTION_MESSAGE); + if (secret == null || secret.trim().isEmpty()) { + showConfigError("No shared secret provided."); + return; + } + sharedSecret = secret.trim(); + + log("Using user-provided WSCP connection config"); + connectToAgent(); + } + + private void showConfigError(String msg) { + statusLabel.setText("Not Connected"); + statusLabel.setForeground(Color.RED); + log(msg); + log("Set FDC3_WEBSOCKET_URL, FDC3_SESSION_ID, and FDC3_CONNECTION_SECRET or restart."); + } + + private void initUI() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(650, 600); + setLocationRelativeTo(null); + + // Main panel with padding + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + // Top panel - Connection status and channel selection + JPanel topPanel = new JPanel(new BorderLayout(10, 5)); + + // Status panel + JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + statusPanel.add(new JLabel("Status:")); + statusLabel = new JLabel("Connecting..."); + statusLabel.setForeground(Color.ORANGE); + statusPanel.add(statusLabel); + + disconnectButton = new JButton("Disconnect"); + disconnectButton.setEnabled(false); + disconnectButton.addActionListener(e -> disconnect()); + statusPanel.add(disconnectButton); + + reconnectButton = new JButton("Reconnect"); + reconnectButton.setEnabled(false); + reconnectButton.setToolTipText("Reconnect using the same instanceId/instanceUuid"); + reconnectButton.addActionListener(e -> reconnect()); + statusPanel.add(reconnectButton); + + topPanel.add(statusPanel, BorderLayout.NORTH); + + // Channel selection panel + JPanel channelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + channelPanel.setBorder(new TitledBorder("User Channel")); + channelPanel.add(new JLabel("Current Channel:")); + channelComboBox = new JComboBox<>(); + channelComboBox.setPreferredSize(new Dimension(200, 25)); + channelComboBox.setEnabled(false); + channelComboBox.addActionListener(e -> onChannelSelected()); + channelPanel.add(channelComboBox); + + JButton leaveButton = new JButton("Leave Channel"); + leaveButton.addActionListener(e -> leaveChannel()); + channelPanel.add(leaveButton); + + topPanel.add(channelPanel, BorderLayout.CENTER); + + mainPanel.add(topPanel, BorderLayout.NORTH); + + // Center panel - Log area + JPanel logPanel = new JPanel(new BorderLayout()); + logPanel.setBorder(new TitledBorder("Context Log")); + + logArea = new JTextArea(); + logArea.setEditable(false); + logArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + JScrollPane scrollPane = new JScrollPane(logArea); + scrollPane.setPreferredSize(new Dimension(550, 250)); + logPanel.add(scrollPane, BorderLayout.CENTER); + + JButton clearButton = new JButton("Clear Log"); + clearButton.addActionListener(e -> logArea.setText("")); + JPanel clearPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + clearPanel.add(clearButton); + logPanel.add(clearPanel, BorderLayout.SOUTH); + + mainPanel.add(logPanel, BorderLayout.CENTER); + + // Bottom panel - contains listener controls and broadcast controls + JPanel bottomPanel = new JPanel(new GridLayout(2, 1, 5, 5)); + + // Listener controls + JPanel listenerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + listenerPanel.setBorder(new TitledBorder("Context Listener")); + + listenerStatusLabel = new JLabel("Listener: Not Active"); + listenerStatusLabel.setForeground(Color.GRAY); + listenerPanel.add(listenerStatusLabel); + + addListenerButton = new JButton("Add Listener"); + addListenerButton.setEnabled(false); + addListenerButton.addActionListener(e -> addContextListener()); + listenerPanel.add(addListenerButton); + + removeListenerButton = new JButton("Remove Listener"); + removeListenerButton.setEnabled(false); + removeListenerButton.addActionListener(e -> removeContextListener()); + listenerPanel.add(removeListenerButton); + + bottomPanel.add(listenerPanel); + + // Broadcast controls + JPanel broadcastPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + broadcastPanel.setBorder(new TitledBorder("Broadcast Context (requires channel)")); + + broadcastInstrumentButton = new JButton("Instrument (MSFT)"); + broadcastInstrumentButton.setEnabled(false); + broadcastInstrumentButton.setToolTipText("Broadcast Microsoft stock instrument context"); + broadcastInstrumentButton.addActionListener(e -> broadcastInstrument()); + broadcastPanel.add(broadcastInstrumentButton); + + broadcastCurrencyButton = new JButton("Currency (USD)"); + broadcastCurrencyButton.setEnabled(false); + broadcastCurrencyButton.setToolTipText("Broadcast US Dollar currency context"); + broadcastCurrencyButton.addActionListener(e -> broadcastCurrency()); + broadcastPanel.add(broadcastCurrencyButton); + + broadcastContactButton = new JButton("Contact (Jane Doe)"); + broadcastContactButton.setEnabled(false); + broadcastContactButton.setToolTipText("Broadcast sample contact context"); + broadcastContactButton.addActionListener(e -> broadcastContact()); + broadcastPanel.add(broadcastContactButton); + + bottomPanel.add(broadcastPanel); + + mainPanel.add(bottomPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // Handle window close - cleanup + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + cleanup(); + } + }); + } + + private void connectToAgent() { + if (websocketUrl == null || sessionId == null || sharedSecret == null) { + log("ERROR: WSCP connection config incomplete"); + return; + } + + log("Connecting to Desktop Agent at: " + websocketUrl); + + try { + GetAgentParams params = GetAgentParams.builder() + .timeoutMs(30000) + .webSocketUrl(websocketUrl) + .sessionId(sessionId) + .sharedSecret(sharedSecret) + .build(); + + GetAgent.getAgent(params) + .thenAccept(this::onAgentConnected) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> onConnectionError(error)); + return null; + }); + } catch (Exception e) { + onConnectionError(e); + } + } + + private void onAgentConnected(DesktopAgent agent) { + this.agent = agent; + SwingUtilities.invokeLater(() -> { + statusLabel.setText("Connected"); + statusLabel.setForeground(new Color(0, 128, 0)); + disconnectButton.setEnabled(true); + reconnectButton.setEnabled(false); + log("Successfully connected to Desktop Agent"); + + // Store connection info for potential reconnection + storeConnectionInfo(); + + // Load user channels + loadUserChannels(); + }); + } + + /** + * Store instanceId and instanceUuid for potential reconnection. + */ + private void storeConnectionInfo() { + if (agent == null) return; + + // Get instanceId and instanceUuid from getInfo() + agent.getInfo() + .thenAccept(info -> { + if (info != null && info.getAppMetadata() != null) { + lastInstanceId = info.getAppMetadata().getInstanceId(); + lastInstanceUuid = info.getAppMetadata().getInstanceUuid(); + + SwingUtilities.invokeLater(() -> { + if (lastInstanceId != null) { + log("Stored instanceId for reconnection: " + lastInstanceId); + } + if (lastInstanceUuid != null) { + log("Stored instanceUuid for reconnection"); + } + }); + } + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("Warning: Could not retrieve connection info - " + error.getMessage())); + return null; + }); + } + + private void onConnectionError(Throwable error) { + statusLabel.setText("Connection Failed"); + statusLabel.setForeground(Color.RED); + disconnectButton.setEnabled(false); + // Enable reconnect if we have stored credentials + reconnectButton.setEnabled(lastInstanceId != null && lastInstanceUuid != null); + log("ERROR: Failed to connect - " + error.getMessage()); + + // Show error dialog with option to retry + int result = JOptionPane.showOptionDialog(this, + "Failed to connect to Desktop Agent:\n" + error.getMessage() + + "\n\nURL: " + websocketUrl + + "\n\nWould you like to enter a different URL?", + "Connection Error", + JOptionPane.YES_NO_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + new String[]{"Enter New URL", "Close"}, + "Enter New URL"); + + if (result == 0) { + promptForConnectionConfig(); + } + } + + /** + * Disconnect from the Desktop Agent. + */ + private void disconnect() { + if (agent == null) return; + + log("Disconnecting from Desktop Agent..."); + + // Clean up listener + if (contextListener != null) { + try { + contextListener.unsubscribe().toCompletableFuture().join(); + contextListener = null; + } catch (Exception e) { + log("Warning: Error unsubscribing listener - " + e.getMessage()); + } + } + + // Disconnect the agent proxy + if (agent instanceof org.finos.fdc3.proxy.DesktopAgentProxy) { + try { + ((org.finos.fdc3.proxy.DesktopAgentProxy) agent).disconnect().toCompletableFuture().join(); + } catch (Exception e) { + log("Warning: Error during disconnect - " + e.getMessage()); + } + } + + agent = null; + currentChannel = null; + + SwingUtilities.invokeLater(() -> { + statusLabel.setText("Disconnected"); + statusLabel.setForeground(Color.GRAY); + disconnectButton.setEnabled(false); + // Enable reconnect if we have stored credentials + reconnectButton.setEnabled(lastInstanceId != null && lastInstanceUuid != null); + channelComboBox.setEnabled(false); + channelComboBox.removeAllItems(); + addListenerButton.setEnabled(false); + removeListenerButton.setEnabled(false); + listenerStatusLabel.setText("Listener: Not Active"); + listenerStatusLabel.setForeground(Color.GRAY); + updateBroadcastButtonsState(); + log("Disconnected from Desktop Agent"); + }); + } + + /** + * Reconnect to the Desktop Agent using the stored instanceId and instanceUuid. + */ + private void reconnect() { + if (websocketUrl == null || sessionId == null) { + log("ERROR: webSocketUrl and sessionId required for reconnection"); + return; + } + + if (lastInstanceId == null || lastInstanceUuid == null) { + log("ERROR: No stored connection info for reconnection. Connect fresh first."); + return; + } + + log("Reconnecting to Desktop Agent..."); + log(" Using instanceId: " + lastInstanceId); + log(" Using instanceUuid: [stored]"); + + statusLabel.setText("Reconnecting..."); + statusLabel.setForeground(Color.ORANGE); + reconnectButton.setEnabled(false); + + try { + GetAgentParams params = GetAgentParams.builder() + .timeoutMs(30000) + .webSocketUrl(websocketUrl) + .sessionId(sessionId) + .instanceId(lastInstanceId) + .instanceUuid(lastInstanceUuid) + .build(); + + GetAgent.getAgent(params) + .thenAccept(this::onAgentConnected) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> onConnectionError(error)); + return null; + }); + } catch (Exception e) { + onConnectionError(e); + } + } + + private void loadUserChannels() { + if (agent == null) return; + + log("Loading user channels..."); + agent.getUserChannels() + .thenAccept(channels -> { + this.userChannels = channels; + SwingUtilities.invokeLater(() -> { + channelComboBox.removeAllItems(); + channelComboBox.addItem(new ChannelItem(null)); // "None" option + for (Channel channel : channels) { + channelComboBox.addItem(new ChannelItem(channel)); + } + channelComboBox.setEnabled(true); + addListenerButton.setEnabled(true); + log("Loaded " + channels.size() + " user channels"); + + // Check current channel + loadCurrentChannel(); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to load channels - " + error.getMessage())); + return null; + }); + } + + private void loadCurrentChannel() { + if (agent == null) return; + + agent.getCurrentChannel() + .thenAccept(optChannel -> { + SwingUtilities.invokeLater(() -> { + if (optChannel.isPresent()) { + currentChannel = optChannel.get(); + // Find and select the channel in the combo box + for (int i = 0; i < channelComboBox.getItemCount(); i++) { + ChannelItem item = channelComboBox.getItemAt(i); + if (item.channel != null && + item.channel.getId().equals(currentChannel.getId())) { + channelComboBox.setSelectedIndex(i); + break; + } + } + log("Current channel: " + currentChannel.getId()); + } else { + currentChannel = null; + channelComboBox.setSelectedIndex(0); + log("Not currently joined to any channel"); + } + updateBroadcastButtonsState(); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to get current channel - " + error.getMessage())); + return null; + }); + } + + private void onChannelSelected() { + if (agent == null || !channelComboBox.isEnabled()) return; + + ChannelItem selected = (ChannelItem) channelComboBox.getSelectedItem(); + if (selected == null) return; + + if (selected.channel == null) { + // "None" selected - leave current channel + if (currentChannel != null) { + leaveChannel(); + } + } else if (currentChannel == null || + !currentChannel.getId().equals(selected.channel.getId())) { + // Join the selected channel + joinChannel(selected.channel); + } + } + + private void joinChannel(Channel channel) { + if (agent == null) return; + + log("Joining channel: " + channel.getId()); + agent.joinUserChannel(channel.getId()) + .thenRun(() -> { + currentChannel = channel; + SwingUtilities.invokeLater(() -> { + log("Joined channel: " + channel.getId()); + updateBroadcastButtonsState(); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> { + log("ERROR: Failed to join channel - " + error.getMessage()); + loadCurrentChannel(); // Refresh to show actual state + }); + return null; + }); + } + + private void leaveChannel() { + if (agent == null) return; + + log("Leaving current channel..."); + agent.leaveCurrentChannel() + .thenRun(() -> { + currentChannel = null; + SwingUtilities.invokeLater(() -> { + channelComboBox.setSelectedIndex(0); + log("Left channel"); + updateBroadcastButtonsState(); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to leave channel - " + error.getMessage())); + return null; + }); + } + + private void addContextListener() { + if (agent == null || contextListener != null) return; + + log("Adding context listener (all types)..."); + agent.addContextListener(null, this::onContextReceived) + .thenAccept(listener -> { + this.contextListener = listener; + SwingUtilities.invokeLater(() -> { + listenerStatusLabel.setText("Listener: Active"); + listenerStatusLabel.setForeground(new Color(0, 128, 0)); + addListenerButton.setEnabled(false); + removeListenerButton.setEnabled(true); + log("Context listener added successfully"); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to add listener - " + error.getMessage())); + return null; + }); + } + + private void removeContextListener() { + if (contextListener == null) return; + + log("Removing context listener..."); + contextListener.unsubscribe() + .thenRun(() -> { + contextListener = null; + SwingUtilities.invokeLater(() -> { + listenerStatusLabel.setText("Listener: Not Active"); + listenerStatusLabel.setForeground(Color.GRAY); + addListenerButton.setEnabled(true); + removeListenerButton.setEnabled(false); + log("Context listener removed"); + }); + }) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to remove listener - " + error.getMessage())); + return null; + }); + } + + /** + * Update the enabled state of broadcast buttons based on channel membership. + */ + private void updateBroadcastButtonsState() { + boolean canBroadcast = currentChannel != null && agent != null; + broadcastInstrumentButton.setEnabled(canBroadcast); + broadcastCurrencyButton.setEnabled(canBroadcast); + broadcastContactButton.setEnabled(canBroadcast); + } + + /** + * Broadcast a sample instrument context (Microsoft stock). + */ + private void broadcastInstrument() { + if (agent == null || currentChannel == null) return; + + Map id = new HashMap<>(); + id.put("ticker", "MSFT"); + id.put("ISIN", "US5949181045"); + + Context instrumentContext = new Context("fdc3.instrument", "Microsoft", id); + + log("Broadcasting instrument context: Microsoft (MSFT)"); + agent.broadcast(instrumentContext) + .thenRun(() -> SwingUtilities.invokeLater(() -> + log("Successfully broadcast instrument context"))) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to broadcast - " + error.getMessage())); + return null; + }); + } + + /** + * Broadcast a sample currency context (US Dollar). + */ + private void broadcastCurrency() { + if (agent == null || currentChannel == null) return; + + Map id = new HashMap<>(); + id.put("CURRENCY_ISOCODE", "USD"); + + Context currencyContext = new Context("fdc3.currency", "US Dollar", id); + + log("Broadcasting currency context: US Dollar (USD)"); + agent.broadcast(currencyContext) + .thenRun(() -> SwingUtilities.invokeLater(() -> + log("Successfully broadcast currency context"))) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to broadcast - " + error.getMessage())); + return null; + }); + } + + /** + * Broadcast a sample contact context (Jane Doe). + */ + private void broadcastContact() { + if (agent == null || currentChannel == null) return; + + Map id = new HashMap<>(); + id.put("email", "jane.doe@mail.com"); + + Context contactContext = new Context("fdc3.contact", "Jane Doe", id); + + log("Broadcasting contact context: Jane Doe"); + agent.broadcast(contactContext) + .thenRun(() -> SwingUtilities.invokeLater(() -> + log("Successfully broadcast contact context"))) + .exceptionally(error -> { + SwingUtilities.invokeLater(() -> + log("ERROR: Failed to broadcast - " + error.getMessage())); + return null; + }); + } + + private void onContextReceived(Context context, ContextMetadata metadata) { + SwingUtilities.invokeLater(() -> { + StringBuilder sb = new StringBuilder(); + sb.append("=== CONTEXT RECEIVED ===\n"); + sb.append("Type: ").append(context.getType()).append("\n"); + + if (metadata != null && metadata.getSource() != null) { + sb.append("Source: ").append(metadata.getSource().getAppId()); + if (metadata.getSource().getInstanceId() != null) { + sb.append(" (").append(metadata.getSource().getInstanceId()).append(")"); + } + sb.append("\n"); + } + + // Log the context details + try { + String contextJson = context.toString(); + sb.append("Data: ").append(contextJson).append("\n"); + } catch (Exception e) { + sb.append("Data: [Error formatting context]\n"); + } + + log(sb.toString()); + }); + } + + private void log(String message) { + String timestamp = LocalDateTime.now().format(TIME_FORMAT); + String logMessage = "[" + timestamp + "] " + message + "\n"; + logArea.append(logMessage); + // Auto-scroll to bottom + logArea.setCaretPosition(logArea.getDocument().getLength()); + } + + private void cleanup() { + if (contextListener != null) { + try { + contextListener.unsubscribe().toCompletableFuture().join(); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + /** + * Helper class to display channels in the combo box + */ + private static class ChannelItem { + final Channel channel; + + ChannelItem(Channel channel) { + this.channel = channel; + } + + @Override + public String toString() { + if (channel == null) { + return "(None)"; + } + String name = channel.getId(); + if (channel.getDisplayMetadata() != null && + channel.getDisplayMetadata().getName() != null) { + name = channel.getDisplayMetadata().getName(); + } + return name + " [" + channel.getId() + "]"; + } + } + + public static void main(String[] args) { + // Set look and feel + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + // Use default look and feel + } + + // Create and show the application + SwingUtilities.invokeLater(() -> { + ExampleApp app = new ExampleApp(); + app.setVisible(true); + }); + } +} diff --git a/fdc3-get-agent/pom.xml b/fdc3-get-agent/pom.xml new file mode 100644 index 00000000..b94884ed --- /dev/null +++ b/fdc3-get-agent/pom.xml @@ -0,0 +1,159 @@ + + + + 4.0.0 + + org.finos.fdc3 + fdc3-parent + 1.0.0-SNAPSHOT + + + fdc3-get-agent + FDC3 Get Agent + Factory for obtaining a DesktopAgent connection via WebSocket + + + UTF-8 + 11 + 11 + 7.15.0 + 6.1.2 + + + + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + + + + org.finos.fdc3 + fdc3-agent-proxy + ${project.version} + + + + + org.finos.fdc3 + fdc3-schema + ${project.version} + + + + + jakarta.websocket + jakarta.websocket-client-api + 2.1.1 + + + + + org.glassfish.tyrus.bundles + tyrus-standalone-client + 2.1.5 + + + + + org.glassfish.tyrus + tyrus-server + 2.1.5 + test + + + org.glassfish.tyrus + tyrus-container-grizzly-server + 2.1.5 + test + + + + + org.slf4j + slf4j-api + 2.0.9 + + + + + io.github.robmoffat + standard-cucumber-steps + 1.2.1 + test + + + io.cucumber + cucumber-spring + ${cucumber.version} + test + + + org.springframework + spring-context + ${spring.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + test + + + org.junit.jupiter + junit-jupiter + 5.10.1 + test + + + org.junit.platform + junit-platform-suite + 1.10.1 + test + + + org.slf4j + slf4j-simple + 2.0.9 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + + + + cucumber.junit-platform.naming-strategy=long + cucumber.junit-platform.discovery.as-root-engine=false + + + + + + + diff --git a/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java new file mode 100644 index 00000000..18d41398 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java @@ -0,0 +1,301 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.errors.FDC3ConnectionException; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.ui.Connectable; +import org.finos.fdc3.proxy.DesktopAgentProxy; +import org.finos.fdc3.proxy.apps.DefaultAppSupport; +import org.finos.fdc3.proxy.channels.DefaultChannelSupport; +import org.finos.fdc3.proxy.heartbeat.DefaultHeartbeatSupport; +import org.finos.fdc3.proxy.intents.DefaultIntentSupport; +import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.ConnectionRole; +import org.finos.fdc3.schema.ProtocolVersion; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequest; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequestMeta; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequestPayload; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequestType; + +/** + * Factory for obtaining a DesktopAgent connection via WebSocket using WSCP. + */ +public class GetAgent { + + private static final String WSCP2_CONNECT_RESPONSE = "WSCP2ConnectResponse"; + private static final String WSCP2_CONNECT_FAILED_RESPONSE = "WSCP2ConnectFailedResponse"; + + private GetAgent() { + } + + public static CompletionStage getAgent(GetAgentParams params) { + Logger.info("Initiating Desktop Agent connection to {}", params.getWebSocketUrl()); + + AppIdentifier tempAppId = new AppIdentifier("pending", null, null); + WebSocketMessaging messaging = new WebSocketMessaging(params.getWebSocketUrl(), tempAppId); + + return messaging.connect() + .thenCompose(v -> performHandshake(messaging, params)) + .thenApply(validationResult -> createDesktopAgent(messaging, validationResult, params)) + .exceptionally(error -> { + try { + messaging.disconnect(); + } catch (Exception e) { + Logger.error("Error during cleanup: {}", e.getMessage()); + } + + if (error.getCause() instanceof FDC3ConnectionException) { + throw (FDC3ConnectionException) error.getCause(); + } + throw new FDC3ConnectionException("Failed to connect to Desktop Agent", error); + }); + } + + private static CompletionStage performHandshake( + WebSocketMessaging messaging, GetAgentParams params) { + + String connectionAttemptUuid = UUID.randomUUID().toString(); + + WebSocketConnectionProtocol1ConnectRequest connectMsg = + new WebSocketConnectionProtocol1ConnectRequest(); + connectMsg.setType(WebSocketConnectionProtocol1ConnectRequestType.WSCP1_CONNECT_REQUEST); + + WebSocketConnectionProtocol1ConnectRequestMeta meta = + new WebSocketConnectionProtocol1ConnectRequestMeta(); + meta.setConnectionAttemptUUID(connectionAttemptUuid); + meta.setTimestamp(OffsetDateTime.now()); + connectMsg.setMeta(meta); + + WebSocketConnectionProtocol1ConnectRequestPayload payload = + new WebSocketConnectionProtocol1ConnectRequestPayload(); + payload.setRole(ConnectionRole.APPLICATION); + payload.setProtocolVersion(ProtocolVersion.THE_10); + payload.setSessionID(params.getSessionId()); + if (params.getSharedSecret() != null) { + payload.setSharedSecret(params.getSharedSecret()); + } + if (params.getAppId() != null) { + payload.setAppID(params.getAppId()); + } + if (params.getInstanceId() != null) { + payload.setInstanceID(params.getInstanceId()); + } + if (params.getInstanceUuid() != null) { + payload.setInstanceUUID(params.getInstanceUuid()); + } + connectMsg.setPayload(payload); + + Map connectMessage = messaging.getConverter().toMap(connectMsg); + + CompletableFuture responseFuture = new CompletableFuture<>(); + + messaging.register(new org.finos.fdc3.proxy.listeners.RegisterableListener() { + private final String id = UUID.randomUUID().toString(); + + @Override + public String getId() { + return id; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!WSCP2_CONNECT_RESPONSE.equals(type) + && !WSCP2_CONNECT_FAILED_RESPONSE.equals(type)) { + return false; + } + + Map msgMeta = (Map) message.get("meta"); + if (msgMeta == null) { + return false; + } + String respConnectionAttemptUuid = (String) msgMeta.get("connectionAttemptUuid"); + return connectionAttemptUuid.equals(respConnectionAttemptUuid); + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + messaging.unregister(id); + + String type = (String) message.get("type"); + Map responsePayload = (Map) message.get("payload"); + + if (WSCP2_CONNECT_FAILED_RESPONSE.equals(type)) { + String errorMessage = responsePayload != null + ? (String) responsePayload.get("message") + : "Connection failed"; + responseFuture.completeExceptionally( + new FDC3ConnectionException("Connection failed: " + errorMessage)); + return; + } + + try { + ValidationResult result = new ValidationResult(); + result.appId = (String) responsePayload.get("appId"); + result.instanceId = (String) responsePayload.get("instanceId"); + result.instanceUuid = (String) responsePayload.get("instanceUuid"); + + Map implMeta = + (Map) responsePayload.get("implementationMetadata"); + if (implMeta != null) { + result.implementationMetadata = parseImplementationMetadata(implMeta); + } + + Logger.info("WSCP handshake successful - appId: {}, instanceId: {}", + result.appId, result.instanceId); + responseFuture.complete(result); + } catch (Exception e) { + responseFuture.completeExceptionally( + new FDC3ConnectionException("Failed to parse WSCP2 response", e)); + } + } + + @Override + public CompletionStage register() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage unsubscribe() { + messaging.unregister(id); + return CompletableFuture.completedFuture(null); + } + }); + + Logger.debug("Sending WSCP1ConnectRequest message"); + messaging.post(connectMessage); + + return responseFuture + .orTimeout(params.getTimeoutMs(), TimeUnit.MILLISECONDS) + .exceptionally(error -> { + if (error instanceof TimeoutException + || (error.getCause() != null + && error.getCause() instanceof TimeoutException)) { + throw new FDC3ConnectionException("Connection timeout waiting for WSCP2 response"); + } + if (error instanceof FDC3ConnectionException) { + throw (FDC3ConnectionException) error; + } + throw new FDC3ConnectionException("Handshake failed", error); + }); + } + + @SuppressWarnings("unchecked") + private static ImplementationMetadata parseImplementationMetadata(Map implMeta) { + ImplementationMetadata metadata = new ImplementationMetadata(); + + metadata.setFdc3Version((String) implMeta.get("fdc3Version")); + metadata.setProvider((String) implMeta.get("provider")); + metadata.setProviderVersion((String) implMeta.get("providerVersion")); + + Map appMeta = (Map) implMeta.get("appMetadata"); + if (appMeta != null) { + org.finos.fdc3.api.metadata.AppMetadata appMetadata = + new org.finos.fdc3.api.metadata.AppMetadata(); + appMetadata.setAppId((String) appMeta.get("appId")); + appMetadata.setInstanceId((String) appMeta.get("instanceId")); + appMetadata.setName((String) appMeta.get("name")); + appMetadata.setVersion((String) appMeta.get("version")); + appMetadata.setTitle((String) appMeta.get("title")); + appMetadata.setTooltip((String) appMeta.get("tooltip")); + appMetadata.setDescription((String) appMeta.get("description")); + metadata.setAppMetadata(appMetadata); + } + + Map optFeatures = (Map) implMeta.get("optionalFeatures"); + if (optFeatures != null) { + ImplementationMetadata.OptionalFeatures features = + new ImplementationMetadata.OptionalFeatures(); + if (optFeatures.get("OriginatingAppMetadata") != null) { + features.setOriginatingAppMetadata((Boolean) optFeatures.get("OriginatingAppMetadata")); + } + if (optFeatures.get("UserChannelMembershipAPIs") != null) { + features.setUserChannelMembershipAPIs( + (Boolean) optFeatures.get("UserChannelMembershipAPIs")); + } + if (optFeatures.get("DesktopAgentBridging") != null) { + features.setDesktopAgentBridging((Boolean) optFeatures.get("DesktopAgentBridging")); + } + metadata.setOptionalFeatures(features); + } + + return metadata; + } + + private static DesktopAgent createDesktopAgent( + WebSocketMessaging messaging, + ValidationResult validationResult, + GetAgentParams params) { + + AppIdentifier appIdentifier = new AppIdentifier( + validationResult.appId, + validationResult.instanceId, + null); + + messaging.setIdentifier(appIdentifier, validationResult.instanceUuid); + + DefaultHeartbeatSupport heartbeatSupport = new DefaultHeartbeatSupport( + messaging, params.getHeartbeatIntervalMs()); + + DefaultChannelSupport channelSupport = new DefaultChannelSupport( + messaging, params.getChannelSelector(), params.getMessageExchangeTimeout()); + + DefaultIntentSupport intentSupport = new DefaultIntentSupport( + messaging, params.getIntentResolver(), + params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); + + DefaultAppSupport appSupport = new DefaultAppSupport( + messaging, params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); + + List connectables = new ArrayList<>(); + connectables.add(heartbeatSupport); + + DesktopAgentProxy proxy = new DesktopAgentProxy( + heartbeatSupport, + channelSupport, + intentSupport, + appSupport, + connectables); + + proxy.connect(); + + Logger.info("DesktopAgent proxy created successfully"); + return proxy; + } + + private static class ValidationResult { + String appId; + String instanceId; + String instanceUuid; + ImplementationMetadata implementationMetadata; + } +} diff --git a/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgentParams.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgentParams.java new file mode 100644 index 00000000..c0f060b9 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgentParams.java @@ -0,0 +1,227 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent; + +import org.finos.fdc3.api.ui.ChannelSelector; +import org.finos.fdc3.api.ui.IntentResolver; +import org.finos.fdc3.getagent.ui.DefaultChannelSelector; +import org.finos.fdc3.getagent.ui.DefaultIntentResolver; + +/** + * Parameters for obtaining a DesktopAgent connection via WebSocket. + *

+ * Uses the WebSocket Connection Protocol (WSCP) for the initial handshake. + *

+ * The following environment variables / system properties provide defaults: + *

    + *
  • {@code FDC3_WEBSOCKET_URL} — deployment WebSocket endpoint (e.g. ws://host/fdc3/ws)
  • + *
  • {@code FDC3_SESSION_ID} — DA user session identifier
  • + *
  • {@code FDC3_CONNECTION_SECRET} — pairing secret or launch token (required for initial connect only)
  • + *
+ */ +public class GetAgentParams { + + public static final String PROP_WEBSOCKET_URL = "FDC3_WEBSOCKET_URL"; + public static final String PROP_SESSION_ID = "FDC3_SESSION_ID"; + public static final String PROP_CONNECTION_SECRET = "FDC3_CONNECTION_SECRET"; + + private final String webSocketUrl; + private final String sessionId; + private final String sharedSecret; + private final String appId; + private final String instanceId; + private final String instanceUuid; + private final ChannelSelector channelSelector; + private final IntentResolver intentResolver; + private final long timeoutMs; + private final long messageExchangeTimeout; + private final long appLaunchTimeout; + private final long heartbeatIntervalMs; + + private GetAgentParams(Builder builder) { + this.webSocketUrl = builder.webSocketUrl; + this.sessionId = builder.sessionId; + this.sharedSecret = builder.sharedSecret; + this.appId = builder.appId; + this.instanceId = builder.instanceId; + this.instanceUuid = builder.instanceUuid; + this.channelSelector = builder.channelSelector; + this.intentResolver = builder.intentResolver; + this.timeoutMs = builder.timeoutMs; + this.messageExchangeTimeout = builder.messageExchangeTimeout; + this.appLaunchTimeout = builder.appLaunchTimeout; + this.heartbeatIntervalMs = builder.heartbeatIntervalMs; + } + + public String getWebSocketUrl() { + return webSocketUrl; + } + + public String getSessionId() { + return sessionId; + } + + public String getSharedSecret() { + return sharedSecret; + } + + public String getAppId() { + return appId; + } + + public String getInstanceId() { + return instanceId; + } + + public String getInstanceUuid() { + return instanceUuid; + } + + public ChannelSelector getChannelSelector() { + return channelSelector; + } + + public IntentResolver getIntentResolver() { + return intentResolver; + } + + public long getTimeoutMs() { + return timeoutMs; + } + + public long getMessageExchangeTimeout() { + return messageExchangeTimeout; + } + + public long getAppLaunchTimeout() { + return appLaunchTimeout; + } + + public long getHeartbeatIntervalMs() { + return heartbeatIntervalMs; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String webSocketUrl = firstNonEmpty( + System.getenv(PROP_WEBSOCKET_URL), + System.getProperty(PROP_WEBSOCKET_URL)); + private String sessionId = firstNonEmpty( + System.getenv(PROP_SESSION_ID), + System.getProperty(PROP_SESSION_ID)); + private String sharedSecret = firstNonEmpty( + System.getenv(PROP_CONNECTION_SECRET), + System.getProperty(PROP_CONNECTION_SECRET)); + private String appId = null; + private String instanceId = null; + private String instanceUuid = null; + private ChannelSelector channelSelector = new DefaultChannelSelector(); + private IntentResolver intentResolver = new DefaultIntentResolver(); + private long timeoutMs = 10000; + private long messageExchangeTimeout = 10000; + private long appLaunchTimeout = 30000; + private long heartbeatIntervalMs = 5000; + + private static String firstNonEmpty(String a, String b) { + if (a != null && !a.isEmpty()) { + return a; + } + if (b != null && !b.isEmpty()) { + return b; + } + return null; + } + + public Builder webSocketUrl(String webSocketUrl) { + this.webSocketUrl = webSocketUrl; + return this; + } + + public Builder sessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + public Builder sharedSecret(String sharedSecret) { + this.sharedSecret = sharedSecret; + return this; + } + + public Builder appId(String appId) { + this.appId = appId; + return this; + } + + public Builder instanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + public Builder instanceUuid(String instanceUuid) { + this.instanceUuid = instanceUuid; + return this; + } + + public Builder channelSelector(ChannelSelector channelSelector) { + this.channelSelector = channelSelector != null ? channelSelector : new DefaultChannelSelector(); + return this; + } + + public Builder intentResolver(IntentResolver intentResolver) { + this.intentResolver = intentResolver != null ? intentResolver : new DefaultIntentResolver(); + return this; + } + + public Builder timeoutMs(long timeoutMs) { + this.timeoutMs = timeoutMs; + return this; + } + + public Builder messageExchangeTimeout(long messageExchangeTimeout) { + this.messageExchangeTimeout = messageExchangeTimeout; + return this; + } + + public Builder appLaunchTimeout(long appLaunchTimeout) { + this.appLaunchTimeout = appLaunchTimeout; + return this; + } + + public Builder heartbeatIntervalMs(long heartbeatIntervalMs) { + this.heartbeatIntervalMs = heartbeatIntervalMs; + return this; + } + + public GetAgentParams build() { + if (webSocketUrl == null || webSocketUrl.isEmpty()) { + throw new IllegalArgumentException("webSocketUrl is required"); + } + if (sessionId == null || sessionId.isEmpty()) { + throw new IllegalArgumentException("sessionId is required"); + } + if ((sharedSecret == null || sharedSecret.isEmpty()) + && (instanceUuid == null || instanceUuid.isEmpty())) { + throw new IllegalArgumentException( + "sharedSecret is required for initial connection, or instanceUuid for reconnect"); + } + return new GetAgentParams(this); + } + } +} diff --git a/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/WebSocketMessaging.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/WebSocketMessaging.java new file mode 100644 index 00000000..59b57858 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/WebSocketMessaging.java @@ -0,0 +1,217 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent; + +import java.io.IOException; +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; + +import jakarta.websocket.ClientEndpoint; +import jakarta.websocket.CloseReason; +import jakarta.websocket.ContainerProvider; +import jakarta.websocket.OnClose; +import jakarta.websocket.OnError; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.WebSocketContainer; + +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.proxy.messaging.AbstractMessaging; +import org.finos.fdc3.proxy.util.Logger; + +/** + * WebSocket-based implementation of the Messaging interface. + *

+ * This class manages the WebSocket connection to the FDC3 Desktop Agent + * and handles sending/receiving messages. + */ +@ClientEndpoint +public class WebSocketMessaging extends AbstractMessaging { + + private final String webSocketUrl; + private final Map listeners = new ConcurrentHashMap<>(); + private Session session; + private CompletableFuture connectionFuture; + private volatile boolean connected = false; + + /** + * Creates a new WebSocketMessaging instance. + * + * @param webSocketUrl the WebSocket URL to connect to + * @param appIdentifier the application identifier + */ + public WebSocketMessaging(String webSocketUrl, AppIdentifier appIdentifier) { + super(appIdentifier); + this.webSocketUrl = webSocketUrl; + } + + /** + * Connects to the WebSocket server. + * + * @return a CompletionStage that completes when the connection is established + */ + public CompletionStage connect() { + if (connected && session != null && session.isOpen()) { + return CompletableFuture.completedFuture(null); + } + + connectionFuture = new CompletableFuture<>(); + + try { + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + container.connectToServer(this, URI.create(webSocketUrl)); + } catch (Exception e) { + connectionFuture.completeExceptionally(e); + } + + return connectionFuture; + } + + @OnOpen + public void onOpen(Session session) { + Logger.info("WebSocket connection opened to {}", webSocketUrl); + this.session = session; + this.connected = true; + if (connectionFuture != null) { + connectionFuture.complete(null); + } + } + + @OnMessage + public void onMessage(String message) { + Logger.debug("Received message: {}", message); + try { + @SuppressWarnings("unchecked") + Map messageMap = getConverter().getObjectMapper().readValue(message, Map.class); + + // Dispatch to all registered listeners + listeners.forEach((id, listener) -> { + try { + if (listener.filter(messageMap)) { + listener.action(messageMap); + } + } catch (Exception e) { + Logger.error("Error in listener {}: {}", id, e.getMessage()); + } + }); + } catch (IOException e) { + Logger.error("Failed to parse message: {}", e.getMessage()); + } + } + + @OnClose + public void onClose(Session session, CloseReason closeReason) { + Logger.info("WebSocket connection closed: {}", closeReason.getReasonPhrase()); + this.connected = false; + this.session = null; + } + + @OnError + public void onError(Session session, Throwable error) { + Logger.error("WebSocket error: {}", error.getMessage()); + if (connectionFuture != null && !connectionFuture.isDone()) { + connectionFuture.completeExceptionally(error); + } + } + + @Override + public String createUUID() { + return UUID.randomUUID().toString(); + } + + @Override + public CompletionStage post(Map message) { + if (session == null || !session.isOpen()) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new IllegalStateException("WebSocket is not connected")); + return future; + } + + try { + String json = getConverter().toJson(message); + Logger.debug("Sending message: {}", json); + session.getAsyncRemote().sendText(json); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(e); + return future; + } + } + + @Override + public void register(RegisterableListener listener) { + if (listener.getId() == null) { + throw new IllegalArgumentException("Listener must have ID set"); + } + listeners.put(listener.getId(), listener); + } + + @Override + public void unregister(String id) { + listeners.remove(id); + } + + @Override + public CompletionStage disconnect() { + if (session == null || !session.isOpen()) { + return CompletableFuture.completedFuture(null); + } + + // Send WSCP3Goodbye message before closing + Map goodbye = new HashMap<>(); + goodbye.put("type", "WSCP3Goodbye"); + Map meta = new HashMap<>(); + meta.put("timestamp", OffsetDateTime.now()); + goodbye.put("meta", meta); + + try { + String json = getConverter().toJson(goodbye); + Logger.debug("Sending message: {}", json); + session.getBasicRemote().sendText(json); + } catch (Exception e) { + Logger.error("Failed to send WSCP3Goodbye: {}", e.getMessage()); + } + + try { + session.close(); + } catch (IOException e) { + Logger.error("Error closing WebSocket: {}", e.getMessage()); + } + + listeners.clear(); + connected = false; + return CompletableFuture.completedFuture(null); + } + + /** + * Returns whether the WebSocket is currently connected. + * + * @return true if connected, false otherwise + */ + public boolean isConnected() { + return connected && session != null && session.isOpen(); + } +} diff --git a/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultChannelSelector.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultChannelSelector.java new file mode 100644 index 00000000..70705907 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultChannelSelector.java @@ -0,0 +1,60 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent.ui; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.ui.ChannelSelector; + +/** + * A default no-op implementation of {@link ChannelSelector} for use when channel selection + * UI is not needed or is handled externally. + *

+ * This implementation does nothing when called - it doesn't display any UI and + * never triggers the channel change callback. Use this when: + *

    + *
  • Your application doesn't use user channels
  • + *
  • Channel selection is handled by a different mechanism
  • + *
  • You're testing without a UI
  • + *
+ */ +public class DefaultChannelSelector implements ChannelSelector { + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public void setChannelChangeCallback(Consumer callback) { + // No-op: this implementation never changes channels + } + + @Override + public void updateChannel(String channelId, List availableChannels) { + // No-op: this implementation doesn't display any UI + } +} diff --git a/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultIntentResolver.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultIntentResolver.java new file mode 100644 index 00000000..81706c0a --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultIntentResolver.java @@ -0,0 +1,150 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent.ui; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.errors.ResolveError; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.ui.IntentResolutionChoice; +import org.finos.fdc3.api.ui.IntentResolver; + +/** + * A configurable default implementation of {@link IntentResolver} for use when intent resolution + * UI is not needed or is handled externally. + *

+ * The behavior can be configured using {@link ResolverBehavior}: + *

    + *
  • {@link ResolverBehavior#USE_FIRST} - Automatically selects the first intent and first app
  • + *
  • {@link ResolverBehavior#CANCEL} - Returns null (indicating user cancellation)
  • + *
  • {@link ResolverBehavior#THROW_ERROR} - Throws a ResolveError
  • + *
+ */ +public class DefaultIntentResolver implements IntentResolver { + + /** + * Defines the behavior of the DefaultIntentResolver when asked to resolve an intent. + */ + public enum ResolverBehavior { + /** + * Automatically select the first intent and first application. + * This is useful for testing or when only one option is expected. + */ + USE_FIRST, + + /** + * Return null, indicating the user cancelled the resolution. + * This will cause the raiseIntent call to fail with UserCancelled. + */ + CANCEL, + + /** + * Throw an error indicating no resolver is available. + * This will cause the raiseIntent call to fail with ResolverUnavailable. + */ + THROW_ERROR + } + + private final ResolverBehavior behavior; + + /** + * Creates a DefaultIntentResolver with the default behavior of {@link ResolverBehavior#USE_FIRST}. + */ + public DefaultIntentResolver() { + this(ResolverBehavior.USE_FIRST); + } + + /** + * Creates a DefaultIntentResolver with the specified behavior. + * + * @param behavior the behavior to use when resolving intents + */ + public DefaultIntentResolver(ResolverBehavior behavior) { + this.behavior = behavior != null ? behavior : ResolverBehavior.USE_FIRST; + } + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage chooseIntent(List appIntents, Context context) { + switch (behavior) { + case USE_FIRST: + return resolveWithFirst(appIntents); + + case CANCEL: + return CompletableFuture.completedFuture(null); + + case THROW_ERROR: + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new RuntimeException(ResolveError.ResolverUnavailable.toString())); + return future; + + default: + return CompletableFuture.completedFuture(null); + } + } + + private CompletionStage resolveWithFirst(List appIntents) { + if (appIntents == null || appIntents.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + + AppIntent firstIntent = appIntents.get(0); + if (firstIntent.getIntent() == null || firstIntent.getApps() == null || firstIntent.getApps().length == 0) { + return CompletableFuture.completedFuture(null); + } + + List apps = Arrays.asList(firstIntent.getApps()); + AppMetadata firstApp = apps.get(0); + + AppIdentifier appIdentifier = new AppIdentifier( + firstApp.getAppId(), + firstApp.getInstanceId(), + firstApp.getDesktopAgent() + ); + + IntentResolutionChoice choice = new IntentResolutionChoice( + firstIntent.getIntent().getName(), + appIdentifier + ); + + return CompletableFuture.completedFuture(choice); + } + + /** + * Gets the behavior configured for this resolver. + * + * @return the resolver behavior + */ + public ResolverBehavior getBehavior() { + return behavior; + } +} diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/utils/StringUtilities.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java similarity index 58% rename from fdc3api/src/main/java/com/finos/fdc3/api/utils/StringUtilities.java rename to fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java index 77af1ed7..fd321677 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/utils/StringUtilities.java +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,16 @@ * limitations under the License. */ -package com.finos.fdc3.api.utils; +package org.finos.fdc3.getagent; -public class StringUtilities -{ -public static String valueAsString(Object object) { - if (object == null) { - return null; - } else { - try { - return JacksonUtilities.getObjectMapper().writeValueAsString(object); - } catch (Throwable t) { - return "N/A"; - } - } -} +import org.springframework.test.context.ContextConfiguration; + +import io.cucumber.spring.CucumberContextConfiguration; + +/** + * Configures Spring for Cucumber test execution. + */ +@CucumberContextConfiguration +@ContextConfiguration(classes = TestSpringConfig.class) +public class CucumberSpringConfiguration { } diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/RunCucumberTest.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/RunCucumberTest.java new file mode 100644 index 00000000..febb41b8 --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/RunCucumberTest.java @@ -0,0 +1,41 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.Suite; + +import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +/** + * JUnit Platform Suite entry point so Maven Surefire discovers Cucumber scenarios. + */ +@Suite +@IncludeEngines("cucumber") +@ConfigurationParameter(key = FEATURES_PROPERTY_NAME, value = "classpath:features") +@ConfigurationParameter( + key = GLUE_PROPERTY_NAME, + value = "org.finos.fdc3.getagent,org.finos.fdc3.getagent.steps,io.github.robmoffat.steps") +@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.spring.SpringFactory") +@ConfigurationParameter( + key = PLUGIN_PROPERTY_NAME, + value = "pretty,summary,junit:target/cucumber-reports/cucumber.xml,html:target/cucumber-reports/cucumber.html") +public class RunCucumberTest {} diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java new file mode 100644 index 00000000..06b6652c --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java @@ -0,0 +1,44 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent; + +import io.github.robmoffat.world.PropsWorld; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ScopedProxyMode; + +import io.cucumber.spring.ScenarioScope; + +/** + * Spring configuration for Cucumber tests. + * + * Scans GetAgent-specific step definitions and generic steps from standard-cucumber-steps. + */ +@Configuration +@ComponentScan(basePackages = { + "org.finos.fdc3.getagent.steps", + "io.github.robmoffat.steps" +}) +public class TestSpringConfig { + + @Bean + @ScenarioScope(proxyMode = ScopedProxyMode.NO) + public PropsWorld propsWorld() { + return new PropsWorld(); + } +} diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/steps/GetAgentSteps.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/steps/GetAgentSteps.java new file mode 100644 index 00000000..d248aa53 --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/steps/GetAgentSteps.java @@ -0,0 +1,116 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent.steps; + +import static io.github.robmoffat.support.MatchingUtils.handleResolve; + +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.getagent.GetAgent; +import org.finos.fdc3.getagent.GetAgentParams; +import org.finos.fdc3.getagent.support.MockWebSocketServer; + +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.github.robmoffat.world.PropsWorld; + +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +/** + * Domain-specific Cucumber steps for GetAgent WSCP integration tests. + */ +public class GetAgentSteps { + + private final PropsWorld world; + + public GetAgentSteps(PropsWorld world) { + this.world = world; + } + + @Before + public void registerGetAgentFunction() { + world.set("getAgent", (Function>) GetAgent::getAgent); + } + + @Given("a mock WebSocket server in {string}") + public void createMockServer(String name) throws Exception { + MockWebSocketServer server = new MockWebSocketServer(); + server.start(); + world.put(name, server); + } + + @Given("{string} will accept pairing for sessionId {string} sharedSecret {string} as appId {string} instanceId {string}") + public void acceptPairing(String serverName, String sessionId, String secret, + String appId, String instanceId) { + getServer(serverName).acceptPairing(sessionId, secret, appId, instanceId); + } + + @Given("{string} will reject pairing with message {string}") + public void rejectPairing(String serverName, String message) { + getServer(serverName).rejectPairing(message); + } + + @Given("{string} will timeout on WSCP handshake") + public void timeoutPairing(String serverName) { + getServer(serverName).timeoutPairing(); + } + + @Given("{string} will return provider {string} fdc3Version {string}") + public void setProvider(String serverName, String provider, String version) { + getServer(serverName).setImplementationMetadata(provider, version); + } + + @Given("{string} is GetAgentParams with webSocketUrl {string} sessionId {string} sharedSecret {string} instanceId {string} instanceUuid {string}") + public void buildParams(String name, String url, String sessionId, String secret, + String instanceId, String instanceUuid) throws Exception { + world.put(name, GetAgentParams.builder() + .webSocketUrl((String) handleResolve(url, world)) + .sessionId(sessionId) + .sharedSecret(secret) + .instanceId(instanceId) + .instanceUuid(instanceUuid) + .build()); + } + + @Given("{string} is GetAgentParams reconnect with webSocketUrl {string} sessionId {string} instanceId {string} instanceUuid {string}") + public void buildReconnectParams(String name, String url, String sessionId, + String instanceId, String instanceUuid) throws Exception { + world.put(name, GetAgentParams.builder() + .webSocketUrl((String) handleResolve(url, world)) + .sessionId(sessionId) + .instanceId(instanceId) + .instanceUuid(instanceUuid) + .build()); + } + + @Given("{string} is GetAgentParams with webSocketUrl {string} sessionId {string} sharedSecret {string} instanceId {string} instanceUuid {string} timeout {long}") + public void buildParamsWithTimeout(String name, String url, String sessionId, String secret, + String instanceId, String instanceUuid, long timeout) throws Exception { + world.put(name, GetAgentParams.builder() + .webSocketUrl((String) handleResolve(url, world)) + .sessionId(sessionId) + .sharedSecret(secret) + .instanceId(instanceId) + .instanceUuid(instanceUuid) + .timeoutMs(timeout) + .build()); + } + + private MockWebSocketServer getServer(String name) { + return (MockWebSocketServer) world.get(name); + } +} diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/support/MockWebSocketServer.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/support/MockWebSocketServer.java new file mode 100644 index 00000000..2815f8d5 --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/support/MockWebSocketServer.java @@ -0,0 +1,293 @@ +/** + * Copyright FINOS and its Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.fdc3.getagent.support; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +import jakarta.websocket.CloseReason; +import jakarta.websocket.OnClose; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.server.ServerEndpoint; + +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.schema.SchemaConverter; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequest; +import org.finos.fdc3.schema.WebSocketConnectionProtocol1ConnectRequestMeta; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectFailedResponse; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectFailedResponsePayload; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectFailedResponseType; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectSuccessResponse; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectSuccessResponsePayload; +import org.finos.fdc3.schema.WebSocketConnectionProtocol2ConnectSuccessResponseType; +import org.finos.fdc3.schema.AddContextListenerResponseMeta; +import org.finos.fdc3.schema.GetInfoRequest; +import org.finos.fdc3.schema.GetInfoResponse; +import org.finos.fdc3.schema.GetInfoResponsePayload; +import org.finos.fdc3.schema.GetInfoResponseType; +import org.finos.fdc3.schema.WebSocketConnectionProtocol3Goodbye; +import org.glassfish.tyrus.server.Server; + +/** + * Mock WebSocket server for testing GetAgent WSCP handshake. + */ +@ServerEndpoint("/fdc3") +public class MockWebSocketServer { + + private static final SchemaConverter converter = new SchemaConverter(); + + private static final CopyOnWriteArrayList sessions = new CopyOnWriteArrayList<>(); + private static volatile MockWebSocketServer currentInstance; + + private Server server; + private int port; + + private String acceptedSessionId; + private String acceptedSharedSecret; + private String responseAppId; + private String responseInstanceId; + private String responseInstanceUuid = "response-uuid"; + private String rejectMessage; + private boolean shouldTimeout; + private String providerName = "test-provider"; + private String fdc3Version = "2.0"; + + private WebSocketConnectionProtocol1ConnectRequest lastWSCP1; + private WebSocketConnectionProtocol3Goodbye lastWSCP3; + + public void start() throws Exception { + port = 8025 + (int) (Math.random() * 1000); + server = new Server("localhost", port, "/", null, MockWebSocketServer.class); + currentInstance = this; + server.start(); + } + + public void stop() { + if (server != null) { + server.stop(); + server = null; + } + currentInstance = null; + sessions.clear(); + } + + public String getUrl() { + return "ws://localhost:" + port + "/fdc3"; + } + + public void acceptPairing(String sessionId, String sharedSecret, String appId, String instanceId) { + acceptedSessionId = sessionId; + acceptedSharedSecret = sharedSecret; + responseAppId = appId; + responseInstanceId = instanceId; + rejectMessage = null; + shouldTimeout = false; + } + + public void rejectPairing(String message) { + acceptedSessionId = null; + acceptedSharedSecret = null; + rejectMessage = message; + shouldTimeout = false; + } + + public void timeoutPairing() { + shouldTimeout = true; + } + + public void setImplementationMetadata(String provider, String version) { + providerName = provider; + fdc3Version = version; + } + + public WebSocketConnectionProtocol1ConnectRequest getLastWSCP1() { + return lastWSCP1; + } + + public WebSocketConnectionProtocol3Goodbye getLastWSCP3() { + return lastWSCP3; + } + + @OnOpen + public void onOpen(Session session) { + sessions.add(session); + } + + @OnClose + public void onClose(Session session, CloseReason reason) { + sessions.remove(session); + } + + @OnMessage + public void onMessage(String message, Session session) { + try { + String type = extractType(message); + MockWebSocketServer instance = currentInstance; + if (instance == null) { + return; + } + + if ("WSCP1ConnectRequest".equals(type)) { + WebSocketConnectionProtocol1ConnectRequest wscp1 = + converter.fromJson(message, WebSocketConnectionProtocol1ConnectRequest.class); + instance.lastWSCP1 = wscp1; + instance.handleConnectRequest(wscp1, session); + } else if ("WSCP3Goodbye".equals(type)) { + WebSocketConnectionProtocol3Goodbye wscp3 = + converter.fromJson(message, WebSocketConnectionProtocol3Goodbye.class); + instance.lastWSCP3 = wscp3; + } else if ("getInfoRequest".equals(type)) { + GetInfoRequest request = converter.fromJson(message, GetInfoRequest.class); + instance.handleGetInfoRequest(request, session); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String extractType(String json) throws IOException { + return converter.getObjectMapper().readTree(json).path("type").asText(null); + } + + private void handleConnectRequest( + WebSocketConnectionProtocol1ConnectRequest request, Session session) { + if (shouldTimeout) { + return; + } + + String connectionAttemptUuid = request.getMeta().getConnectionAttemptUUID(); + String sessionId = request.getPayload().getSessionID(); + String sharedSecret = request.getPayload().getSharedSecret(); + String instanceUuid = request.getPayload().getInstanceUUID(); + + try { + String responseJson; + + if (rejectMessage != null) { + responseJson = buildFailedResponse(connectionAttemptUuid, rejectMessage); + } else if (acceptedSessionId != null + && acceptedSessionId.equals(sessionId) + && instanceUuid != null + && instanceUuid.equals(responseInstanceUuid)) { + // Flow 2: reconnect with sessionId + instanceUuid (no sharedSecret) + responseJson = buildSuccessResponse(connectionAttemptUuid); + } else if (acceptedSessionId != null + && acceptedSessionId.equals(sessionId) + && acceptedSharedSecret != null + && acceptedSharedSecret.equals(sharedSecret)) { + if (instanceUuid != null) { + responseInstanceUuid = instanceUuid; + } + responseJson = buildSuccessResponse(connectionAttemptUuid); + } else { + responseJson = buildFailedResponse(connectionAttemptUuid, "Invalid pairing credentials"); + } + + session.getBasicRemote().sendText(responseJson); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void handleGetInfoRequest(GetInfoRequest request, Session session) throws IOException { + GetInfoResponse response = new GetInfoResponse(); + response.setType(GetInfoResponseType.GET_INFO_RESPONSE); + + AddContextListenerResponseMeta meta = new AddContextListenerResponseMeta(); + meta.setRequestUUID(request.getMeta().getRequestUUID()); + meta.setResponseUUID(UUID.randomUUID().toString()); + meta.setTimestamp(OffsetDateTime.now()); + meta.setSource(request.getMeta().getSource()); + response.setMeta(meta); + + GetInfoResponsePayload payload = new GetInfoResponsePayload(); + payload.setImplementationMetadata(buildImplementationMetadata()); + response.setPayload(payload); + + session.getBasicRemote().sendText(converter.toJson(response)); + } + + private ImplementationMetadata buildImplementationMetadata() { + ImplementationMetadata implMeta = new ImplementationMetadata(); + implMeta.setFdc3Version(fdc3Version); + implMeta.setProvider(providerName); + implMeta.setProviderVersion("1.0.0"); + + AppMetadata appMeta = new AppMetadata(); + appMeta.setAppId(responseAppId); + appMeta.setInstanceId(responseInstanceId); + implMeta.setAppMetadata(appMeta); + + ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); + optFeatures.setOriginatingAppMetadata(true); + optFeatures.setUserChannelMembershipAPIs(true); + optFeatures.setDesktopAgentBridging(false); + implMeta.setOptionalFeatures(optFeatures); + + return implMeta; + } + + private String buildSuccessResponse(String connectionAttemptUuid) throws IOException { + WebSocketConnectionProtocol2ConnectSuccessResponse response = + new WebSocketConnectionProtocol2ConnectSuccessResponse(); + + WebSocketConnectionProtocol1ConnectRequestMeta meta = + new WebSocketConnectionProtocol1ConnectRequestMeta(); + meta.setConnectionAttemptUUID(connectionAttemptUuid); + meta.setTimestamp(OffsetDateTime.now()); + response.setMeta(meta); + + response.setType( + WebSocketConnectionProtocol2ConnectSuccessResponseType.WSCP2_CONNECT_RESPONSE); + + WebSocketConnectionProtocol2ConnectSuccessResponsePayload payload = + new WebSocketConnectionProtocol2ConnectSuccessResponsePayload(); + payload.setAppID(responseAppId); + payload.setInstanceID(responseInstanceId); + payload.setInstanceUUID(responseInstanceUuid); + payload.setImplementationMetadata(buildImplementationMetadata()); + response.setPayload(payload); + + return converter.toJson(response); + } + + private String buildFailedResponse(String connectionAttemptUuid, String message) throws IOException { + WebSocketConnectionProtocol2ConnectFailedResponse response = + new WebSocketConnectionProtocol2ConnectFailedResponse(); + + WebSocketConnectionProtocol1ConnectRequestMeta meta = + new WebSocketConnectionProtocol1ConnectRequestMeta(); + meta.setConnectionAttemptUUID(connectionAttemptUuid); + meta.setTimestamp(OffsetDateTime.now()); + response.setMeta(meta); + + response.setType( + WebSocketConnectionProtocol2ConnectFailedResponseType.WSCP2_CONNECT_FAILED_RESPONSE); + + WebSocketConnectionProtocol2ConnectFailedResponsePayload payload = + new WebSocketConnectionProtocol2ConnectFailedResponsePayload(); + payload.setMessage(message); + response.setPayload(payload); + + return converter.toJson(response); + } +} diff --git a/fdc3-get-agent/src/test/resources/features/get-agent.feature b/fdc3-get-agent/src/test/resources/features/get-agent.feature new file mode 100644 index 00000000..ff3f6a61 --- /dev/null +++ b/fdc3-get-agent/src/test/resources/features/get-agent.feature @@ -0,0 +1,59 @@ +Feature: GetAgent WebSocket Connection + + Background: Test Setup + Given a mock WebSocket server in "server" + + Scenario: Successful connection with valid pairing + Given "server" will accept pairing for sessionId "test-session" sharedSecret "test-secret" as appId "test-app" instanceId "test-instance" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "test-secret" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{params}" + And "{result}" is not null + When I call "{server}" with "getLastWSCP1" + And I refer to "{result}" as "wscp1" + Then "{wscp1}" is an object with the following contents + | payload.sessionID | payload.sharedSecret | payload.instanceID | payload.instanceUUID | + | test-session | test-secret | test-instance | test-uuid-123 | + + Scenario: Connection fails with invalid pairing + Given "server" will reject pairing with message "Access denied" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "wrong-secret" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{params}" + And "{result}" is an error with message "Connection failed: Access denied" + + Scenario: Connection times out + Given "server" will timeout on WSCP handshake + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "test-secret" instanceId "test-instance" instanceUuid "test-uuid-123" timeout 2000 + When I wait for "{getAgent}" using argument "{params}" + And "{result}" is an error + + Scenario: GetAgent returns implementation metadata + Given "server" will accept pairing for sessionId "test-session" sharedSecret "test-secret" as appId "test-app" instanceId "test-instance" + Given "server" will return provider "test-provider" fdc3Version "2.0" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "test-secret" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{params}" + When I call "{result}" with "getInfo" + And "{result}" is an object with the following contents + | provider | fdc3Version | + | test-provider | 2.0 | + + Scenario: Reconnect with instanceUuid and no sharedSecret + Given "server" will accept pairing for sessionId "test-session" sharedSecret "test-secret" as appId "test-app" instanceId "test-instance" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "test-secret" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{params}" + Given "reconnectParams" is GetAgentParams reconnect with webSocketUrl "{server.url}" sessionId "test-session" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{reconnectParams}" + When I call "{server}" with "getLastWSCP1" + And I refer to "{result}" as "wscp1reconnect" + Then "{wscp1reconnect}" is an object with the following contents + | payload.sessionID | payload.sharedSecret | payload.instanceID | payload.instanceUUID | + | test-session | {null} | test-instance | test-uuid-123 | + + Scenario: Disconnect sends WSCP3Goodbye + Given "server" will accept pairing for sessionId "test-session" sharedSecret "test-secret" as appId "test-app" instanceId "test-instance" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" sessionId "test-session" sharedSecret "test-secret" instanceId "test-instance" instanceUuid "test-uuid-123" + When I wait for "{getAgent}" using argument "{params}" + When I refer to "{result}" as "agent" + When I call "{agent}" with "disconnect" + When I call "{server}" with "getLastWSCP3" + And I refer to "{result}" as "wscp3" + Then "{wscp3}" is not null diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml new file mode 100644 index 00000000..50da95ce --- /dev/null +++ b/fdc3-schema/pom.xml @@ -0,0 +1,316 @@ + + + 4.0.0 + + org.finos.fdc3 + fdc3-parent + 1.0.0-SNAPSHOT + + + fdc3-schema + FDC3 Schema Types + Generated Java classes for FDC3 API types from JSON Schema + + + UTF-8 + 11 + 11 + v20.11.0 + 10.2.4 + 2.2.3 + ${project.build.directory}/schema-work + ${project.build.directory}/npm-work/node_modules/@finos/fdc3-schema/dist/schemas + ${project.build.directory}/npm-work/node_modules/@finos/fdc3-context/dist/schemas/context + ${fdc3.schema.work.dir}/api/api.schema.json + install @finos/fdc3-schema@${fdc3.schema.version} @finos/fdc3-context@${fdc3.schema.version} quicktype --save + + + + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.16.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.16.1 + + + + + org.junit.jupiter + junit-jupiter + 5.10.1 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.source.version} + ${jdk.target.version} + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + populate-schema-work + process-sources + + copy-resources + + + ${fdc3.schema.work.dir} + true + + + ${fdc3.schema.source.dir}/api + api + + + ${fdc3.schema.source.dir}/bridging + bridging + + + ${fdc3.schema.source.dir}/bridgingAsyncAPI + bridgingAsyncAPI + + + ${fdc3.context.source.dir} + context + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + create-directories + initialize + + run + + + + + + + + + + quicktype-api + process-sources + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + delete-duplicate-classes + process-sources + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.15.0 + + ${node.version} + ${npm.version} + ${project.build.directory}/npm-work + ${project.build.directory}/node-installation + + + + install-node-and-npm + generate-sources + + install-node-and-npm + + + + npm-init + generate-sources + + npm + + + init -y + + + + npm-install-packages + generate-sources + + npm + + + ${fdc3.npm.schema.packages} + + + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.3 + + + add-standard-imports + process-sources + + replace + + + ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema + + *.java + + + + ^package org\.finos\.fdc3\.schema; + package org.finos.fdc3.schema; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.*; +import org.finos.fdc3.api.types.AppIdentifier; + + + true + + MULTILINE + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-source + process-sources + + add-source + + + + ${project.build.directory}/generated-sources/fdc3 + + + + + + + + + + + + download + + + + + local + + true + + + ${project.basedir}/src/main/schemas-temp/3.0.0 + ${project.basedir}/../fdc3-context/src/schemas-temp/3.0.0/context + install quicktype --save + + + + diff --git a/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java b/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java new file mode 100644 index 00000000..8e56807f --- /dev/null +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java @@ -0,0 +1,96 @@ +package org.finos.fdc3.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Jackson Mix-ins for FDC3 schema types to handle optional fields. + * + * Generated by scripts/generate-mixins.js - regenerate after schema changes. + * + * In JSON Schema, optional fields (not in "required" array) that don't allow null + * should be omitted when null, not serialized as "field": null. + * These mix-ins apply @JsonInclude(NON_NULL) to those fields. + * + * Note: Some types (AppIdentifier, AppMetadata, DisplayMetadata, Icon, Image, + * ImplementationMetadata, IntentMetadata) are now beans in fdc3-standard with + * @JsonInclude(NON_NULL) at class level, so they don't need mixins here. + */ +public final class NullHandlingMixin { + + private NullHandlingMixin() { + // Utility class + } + + /** + * Register all mix-ins with the given ObjectMapper. + * Note: Types from fdc3-standard (AppIdentifier, AppMetadata, DisplayMetadata, + * Icon, Image, ImplementationMetadata, IntentMetadata) already have proper + * JSON annotations and don't need mixins. + */ + public static void registerAll(ObjectMapper om) { + om.addMixIn(BroadcastEventPayload.class, BroadcastEventPayloadMixin.class); + om.addMixIn(Channel.class, ChannelMixin.class); + om.addMixIn(FindIntentRequestPayload.class, FindIntentRequestPayloadMixin.class); + om.addMixIn(FindIntentsByContextRequestPayload.class, FindIntentsByContextRequestPayloadMixin.class); + om.addMixIn(IntentEventPayload.class, IntentEventPayloadMixin.class); + om.addMixIn(OpenRequestPayload.class, OpenRequestPayloadMixin.class); + om.addMixIn(RaiseIntentForContextRequestPayload.class, RaiseIntentForContextRequestPayloadMixin.class); + om.addMixIn(RaiseIntentRequestPayload.class, RaiseIntentRequestPayloadMixin.class); + } + + // === Mix-in classes for schema types that need null handling === + + public static abstract class BroadcastEventPayloadMixin { + @JsonProperty("originatingApp") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getOriginatingApp(); + } + + public static abstract class ChannelMixin { + @JsonProperty("displayMetadata") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getDisplayMetadata(); + } + + public static abstract class FindIntentRequestPayloadMixin { + @JsonProperty("context") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getContext(); + + @JsonProperty("resultType") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getResultType(); + } + + public static abstract class FindIntentsByContextRequestPayloadMixin { + @JsonProperty("resultType") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getResultType(); + } + + public static abstract class IntentEventPayloadMixin { + @JsonProperty("originatingApp") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getOriginatingApp(); + } + + public static abstract class OpenRequestPayloadMixin { + @JsonProperty("context") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getContext(); + } + + public static abstract class RaiseIntentForContextRequestPayloadMixin { + @JsonProperty("app") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getApp(); + } + + public static abstract class RaiseIntentRequestPayloadMixin { + @JsonProperty("app") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getApp(); + } +} diff --git a/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java b/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java new file mode 100644 index 00000000..84c6a716 --- /dev/null +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java @@ -0,0 +1,235 @@ +package org.finos.fdc3.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Converter for JSON to FDC3 API message types based on the "type" field in the JSON. + */ +public class SchemaConverter { + + private final ObjectMapper mapper; + + // Map of FDC3 message type strings to their corresponding Java classes + private static final Map> TYPE_MAP = new HashMap<>(); + + static { + // Request messages + TYPE_MAP.put("addContextListenerRequest", AddContextListenerRequest.class); + TYPE_MAP.put("addEventListenerRequest", AddEventListenerRequest.class); + TYPE_MAP.put("addIntentListenerRequest", AddIntentListenerRequest.class); + TYPE_MAP.put("broadcastRequest", BroadcastRequest.class); + TYPE_MAP.put("contextListenerUnsubscribeRequest", ContextListenerUnsubscribeRequest.class); + TYPE_MAP.put("createPrivateChannelRequest", CreatePrivateChannelRequest.class); + TYPE_MAP.put("eventListenerUnsubscribeRequest", EventListenerUnsubscribeRequest.class); + TYPE_MAP.put("findInstancesRequest", FindInstancesRequest.class); + TYPE_MAP.put("findIntentRequest", FindIntentRequest.class); + TYPE_MAP.put("findIntentsByContextRequest", FindIntentsByContextRequest.class); + TYPE_MAP.put("getCurrentChannelRequest", GetCurrentChannelRequest.class); + TYPE_MAP.put("getCurrentContextRequest", GetCurrentContextRequest.class); + TYPE_MAP.put("getInfoRequest", GetInfoRequest.class); + TYPE_MAP.put("getOrCreateChannelRequest", GetOrCreateChannelRequest.class); + TYPE_MAP.put("getUserChannelsRequest", GetUserChannelsRequest.class); + TYPE_MAP.put("heartbeatAcknowledgementRequest", HeartbeatAcknowledgementRequest.class); + TYPE_MAP.put("intentListenerUnsubscribeRequest", IntentListenerUnsubscribeRequest.class); + TYPE_MAP.put("intentResultRequest", IntentResultRequest.class); + TYPE_MAP.put("joinUserChannelRequest", JoinUserChannelRequest.class); + TYPE_MAP.put("leaveCurrentChannelRequest", LeaveCurrentChannelRequest.class); + TYPE_MAP.put("openRequest", OpenRequest.class); + TYPE_MAP.put("privateChannelAddEventListenerRequest", PrivateChannelAddEventListenerRequest.class); + TYPE_MAP.put("privateChannelDisconnectRequest", PrivateChannelDisconnectRequest.class); + TYPE_MAP.put("privateChannelUnsubscribeEventListenerRequest", PrivateChannelUnsubscribeEventListenerRequest.class); + TYPE_MAP.put("raiseIntentForContextRequest", RaiseIntentForContextRequest.class); + TYPE_MAP.put("raiseIntentRequest", RaiseIntentRequest.class); + + // Response messages + TYPE_MAP.put("addContextListenerResponse", AddContextListenerResponse.class); + TYPE_MAP.put("addEventListenerResponse", AddEventListenerResponse.class); + TYPE_MAP.put("addIntentListenerResponse", AddIntentListenerResponse.class); + TYPE_MAP.put("broadcastResponse", BroadcastResponse.class); + TYPE_MAP.put("contextListenerUnsubscribeResponse", ContextListenerUnsubscribeResponse.class); + TYPE_MAP.put("createPrivateChannelResponse", CreatePrivateChannelResponse.class); + TYPE_MAP.put("eventListenerUnsubscribeResponse", EventListenerUnsubscribeResponse.class); + TYPE_MAP.put("findInstancesResponse", FindInstancesResponse.class); + TYPE_MAP.put("findIntentResponse", FindIntentResponse.class); + TYPE_MAP.put("findIntentsByContextResponse", FindIntentsByContextResponse.class); + TYPE_MAP.put("getCurrentChannelResponse", GetCurrentChannelResponse.class); + TYPE_MAP.put("getCurrentContextResponse", GetCurrentContextResponse.class); + TYPE_MAP.put("getInfoResponse", GetInfoResponse.class); + TYPE_MAP.put("getOrCreateChannelResponse", GetOrCreateChannelResponse.class); + TYPE_MAP.put("getUserChannelsResponse", GetUserChannelsResponse.class); + TYPE_MAP.put("intentListenerUnsubscribeResponse", IntentListenerUnsubscribeResponse.class); + TYPE_MAP.put("intentResultResponse", IntentResultResponse.class); + TYPE_MAP.put("joinUserChannelResponse", JoinUserChannelResponse.class); + TYPE_MAP.put("leaveCurrentChannelResponse", LeaveCurrentChannelResponse.class); + TYPE_MAP.put("openResponse", OpenResponse.class); + TYPE_MAP.put("privateChannelAddEventListenerResponse", PrivateChannelAddEventListenerResponse.class); + TYPE_MAP.put("privateChannelDisconnectResponse", PrivateChannelDisconnectResponse.class); + TYPE_MAP.put("privateChannelUnsubscribeEventListenerResponse", PrivateChannelUnsubscribeEventListenerResponse.class); + TYPE_MAP.put("raiseIntentForContextResponse", RaiseIntentForContextResponse.class); + TYPE_MAP.put("raiseIntentResponse", RaiseIntentResponse.class); + TYPE_MAP.put("raiseIntentResultResponse", RaiseIntentResultResponse.class); + + // Event messages + TYPE_MAP.put("broadcastEvent", BroadcastEvent.class); + TYPE_MAP.put("channelChangedEvent", ChannelChangedEvent.class); + TYPE_MAP.put("heartbeatEvent", HeartbeatEvent.class); + TYPE_MAP.put("intentEvent", IntentEvent.class); + TYPE_MAP.put("privateChannelOnAddContextListenerEvent", PrivateChannelOnAddContextListenerEvent.class); + TYPE_MAP.put("privateChannelOnDisconnectEvent", PrivateChannelOnDisconnectEvent.class); + TYPE_MAP.put("privateChannelOnUnsubscribeEvent", PrivateChannelOnUnsubscribeEvent.class); + } + + /** + * Creates a new SchemaConverter with a default ObjectMapper configuration. + */ + public SchemaConverter() { + this.mapper = createObjectMapper(); + } + + /** + * Creates a new SchemaConverter with a custom ObjectMapper. + * + * @param mapper the ObjectMapper to use + */ + public SchemaConverter(ObjectMapper mapper) { + this.mapper = mapper; + } + + private static ObjectMapper createObjectMapper() { + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()); + om.registerModule(new Jdk8Module()); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + + // Register mix-ins to handle optional fields that should be omitted when null + NullHandlingMixin.registerAll(om); + + return om; + } + + /** + * Get the Java class for a given FDC3 message type string. + * + * @param messageType the FDC3 message type string (e.g., "broadcastRequest") + * @return the corresponding Java class, or null if not found + */ + public Class getClassForType(String messageType) { + return TYPE_MAP.get(messageType); + } + + /** + * Parse a JSON string and return the appropriate message object based on the "type" field. + * + * @param json the JSON string to parse + * @return the parsed message object + * @throws IOException if parsing fails + * @throws IllegalArgumentException if the type is unknown + */ + public Object fromJson(String json) throws IOException { + JsonNode node = mapper.readTree(json); + String type = node.has("type") ? node.get("type").asText() : null; + + if (type == null) { + throw new IllegalArgumentException("JSON does not contain a 'type' field"); + } + + Class clazz = TYPE_MAP.get(type); + if (clazz == null) { + throw new IllegalArgumentException("Unknown message type: " + type); + } + + return mapper.treeToValue(node, clazz); + } + + /** + * Parse a JSON string into a specific message class. + * + * @param json the JSON string to parse + * @param clazz the target class + * @param the type of the message + * @return the parsed message object + * @throws IOException if parsing fails + */ + public T fromJson(String json, Class clazz) throws IOException { + return mapper.readValue(json, clazz); + } + + /** + * Convert an object to another type (e.g., Map to typed object). + * + * @param fromValue the source object + * @param toValueType the target type + * @param the type to convert to + * @return the converted object + */ + public T convertValue(Object fromValue, Class toValueType) { + return mapper.convertValue(fromValue, toValueType); + } + + /** + * Convert an object to a Map. + * + * @param value the object to convert + * @return the object as a Map + */ + @SuppressWarnings("unchecked") + public Map toMap(Object value) { + return mapper.convertValue(value, Map.class); + } + + /** + * Serialize a message object to JSON. + * + * @param message the message object to serialize + * @return the JSON string + * @throws JsonProcessingException if serialization fails + */ + public String toJson(Object message) throws JsonProcessingException { + return mapper.writeValueAsString(message); + } + + /** + * Serialize a message object to pretty-printed JSON. + * + * @param message the message object to serialize + * @return the pretty-printed JSON string + * @throws JsonProcessingException if serialization fails + */ + public String toJsonPretty(Object message) throws JsonProcessingException { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message); + } + + /** + * Check if two JSON strings are semantically equivalent (same content, possibly different formatting). + * + * @param json1 first JSON string + * @param json2 second JSON string + * @return true if the JSON objects are equivalent + * @throws IOException if parsing fails + */ + public boolean jsonEquals(String json1, String json2) throws IOException { + JsonNode node1 = mapper.readTree(json1); + JsonNode node2 = mapper.readTree(json2); + return node1.equals(node2); + } + + /** + * Get the ObjectMapper used by this converter. + * + * @return the ObjectMapper instance + */ + public ObjectMapper getObjectMapper() { + return mapper; + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/README.md b/fdc3-schema/src/main/schemas-temp/3.0.0/api/README.md new file mode 100644 index 00000000..950db8dc --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/README.md @@ -0,0 +1,29 @@ +# Intro + +The _schemas/api_ folder contains JSONSchema definitions that are used to implement wire protocols for an app working with a Desktop Agent, and for import into the Bridging wire protocols that shares many of the same structures. + +Please note: Quicktype, the chosen generation tool currently has some limitations that prevent fully automatic schema generation from the existing TS types. For example, it can not handle interfaces that contain methods in their definition (as you can't define methods in JSON). It also fails to generate schemas even if a type contains unused references to other types or interfaces that contain async functions (`Promise` return types). Therefore, in order to generate the `api\schemas\api.schema.json` some manual intervention was needed. + +Once these limitations are not an issue the `api\schemas\t2sQuicktypeUtil.js` script should be moved to the root level of the project and a new npm script `"api-schema-gen": "node t2sQuicktypeUtil.js src/api schemas/api/api.schema.json"` should be added. Alternatively, schemas (for API types) may be manually maintained against the matching TypeScript definitions + +Contents: + +- `api\schemas\t2sQuicktypeUtil.js` - Script used to run the generation of the schema from the types. Should be moved to the root level of the repo once fully-automated generation can be achieved. +- `api\schemas\api.schema.json` - Partially auto-generated schema from the existing `src\api` types and metadata objects. Expected to be manually maintained in future. +- `api\schemas\common.schema.json` - Common element definitions referenced in multiple other schemas in both the API and Bridging API protocols. +- `api\schemas\appRequest.schema.json` - The base message definition that requests from an app to the DA are derived from. +- `api\schemas\agentResponse.schema.json` - The base message definition that API call response messages from a DA to an app are derived from. +- `api\schemas\agentEvent.schema.json` - The base message definition that event messages from a DA to an app are derived from. +- `api\schemas\*Request.schema.json` - Schemas defining request messages sent from apps to Desktop Agents. +- `api\schemas\*Response.schema.json` - Schemas defining responses from DAs to apps for request messages (sent from apps to Desktop Agents). +- `api\schemas\*Event.schema.json` - Schemas defining event messages sent from Desktop Agents to Apps. + +Please note that when adding a particular message type, that it needs its own schema file, which will declare the type (string). That type string MUST also be added to an enumeration in the base message schema it was derived from - each base message schema (appRequest, agentResponse, agentEvent) has an enumeration of the valid types and it must appear in that or the message will not validate. Unhelpfully, if you've forgotten to do that Quicktype will only report: + +``` +Error: Internal error: We got an empty string type. +``` + +or another similar error - its not always the same one! + +It can be very hard to figure out in which file the problem occurs. Generally, to figure out where an issue is, you can enable Quicktype's debug output by adding `--debug all` or `--debug print-schema-resolving` to the arguments assembled in s2tQuicktypeUtil.js or by taking the command it constructs (printed to the console) and manually add the option and re-run the command. If you're lucky, the error will be hit during the resolution steps which will point to the file(s) with an issue (often a disagreement between types combined with `allOf`). diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP1Hello.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP1Hello.schema.json new file mode 100644 index 00000000..2aab16e3 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP1Hello.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP1Hello.schema.json", + "title": "Web Connection Protocol 1 Hello", + "description": "Hello message sent by an application to a parent window or frame when attempting to establish connectivity to a Desktop Agent.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP1HelloBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP1HelloBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP1Hello Message Type", + "const": "WCP1Hello" + }, + "payload": { + "title": "WCP1Hello Payload", + "type": "object", + "properties": { + "identityUrl": { + "title": "Identity URL", + "description": "URL to use for the identity of the application. Desktop Agents MUST validate that the origin of the message matches the URL, but MAY implement custom comparison logic.", + "type": "string", + "format": "uri" + }, + "actualUrl": { + "title": "Actual URL", + "description": "The current URL of the page attempting to connect. This may differ from the identityUrl, but the origins MUST match.", + "type": "string", + "format": "uri" + }, + "fdc3Version": { + "title": "FDC3 version", + "description": "The version of FDC3 API that the app supports.", + "type": "string" + }, + "intentResolver": { + "title": "Intent Resolver Required", + "description": "A flag that may be used to indicate that an intent resolver is or is not required. Set to `false` if no intents, or only targeted intents, are raised.", + "type": "boolean" + }, + "channelSelector": { + "title": "Channel Selector Required", + "description": "A flag that may be used to indicate that a channel selector user interface is or is not required. Set to `false` if the app includes its own interface for selecting channels or does not work with user channels.", + "type": "boolean" + } + }, + "required": ["identityUrl","actualUrl","fdc3Version"], + "additionalProperties": false + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ "type", "payload", "meta"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP2LoadUrl.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP2LoadUrl.schema.json new file mode 100644 index 00000000..1ba07d4b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP2LoadUrl.schema.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP2LoadUrl.schema.json", + "title": "Web Connection Protocol 2 Load Url", + "description": "Response from a Desktop Agent to an application requesting access to it indicating that it should load a specified URL into a hidden iframe in order to establish connectivity to a Desktop Agent.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP2LoadUrlBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP2LoadUrlBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP2LoadUrl Message Type", + "const": "WCP2LoadUrl" + }, + "payload": { + "title": "WCP2LoadUrl Payload", + "type": "object", + "properties": { + "iframeUrl": { + "title": "iframe URL", + "type": "string", + "description": "A URL which can be used to establish communication with the Desktop Agent, via loading the URL into an iframe and restarting the Web Connection protocol with the iframe as the target.", + "format": "uri" + } + }, + "required": [ + "iframeUrl" + ], + "additionalProperties": false + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP3Handshake.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP3Handshake.schema.json new file mode 100644 index 00000000..fee4edb1 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP3Handshake.schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP3Handshake.schema.json", + "title": "Web Connection Protocol 3 Handshake", + "description": "Handshake message sent by the Desktop Agent to the app (with a MessagePort appended) that should be used for subsequent communication steps.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP3HandshakeBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP3HandshakeBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP3Handshake Message Type", + "const": "WCP3Handshake" + }, + "payload": { + "title": "WCP3Handshake Payload", + "type": "object", + "properties": { + "fdc3Version": { + "title": "FDC3 version", + "type": "string", + "description": "The version of FDC3 API that the Desktop Agent will provide support for." + }, + "intentResolverUrl": { + "title": "Resolver URL", + "description": "Indicates whether an intent resolver user interface is required and the URL to use to do so. Set to `true` to use the default or `false` to disable the intent resolver (as the Desktop Agent will handle it another way).", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "boolean" + } + ] + }, + "channelSelectorUrl": { + "title": "Channel Selector URL", + "description": "Indicates whether a channel selector user interface is required and the URL to use to do so. Set to `true` to use the default or `false` to disable the channel selector (as the Desktop Agent will handle it another way).", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "boolean" + } + ] + }, + "messageExchangeTimeout": { + "title": "Message Exchange Timeout", + "description": "Indicates a custom timeout (in milliseconds) that should be used for the majority of API message exchanges instead of the default 10,000 millisecond timeout.", + "type": "number", + "minimum": 100 + }, + "appLaunchTimeout": { + "title": "App Launch Timeout", + "description": "Indicates a custom timeout (in milliseconds) that should be used for API message exchanges that may involve launching an application, instead of the default 100,000 millisecond timeout.", + "type": "number", + "minimum": 15000 + } + }, + "additionalProperties": false, + "required": [ + "fdc3Version", + "intentResolverUrl", + "channelSelectorUrl" + ] + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP4ValidateAppIdentity.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP4ValidateAppIdentity.schema.json new file mode 100644 index 00000000..b2d4ace6 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP4ValidateAppIdentity.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP4ValidateAppIdentity.schema.json", + "title": "Web Connection Protocol 4 Validate App Identity", + "description": "Identity Validation request from an app attempting to connect to a Desktop Agent.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP4ValidateAppIdentityBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP4ValidateAppIdentityBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP4ValidateAppIdentity Message Type", + "const": "WCP4ValidateAppIdentity" + }, + "payload": { + "title": "WCP4ValidateAppIdentity Payload", + "type": "object", + "properties": { + "identityUrl": { + "title": "Identity URL", + "description": "URL to use for the identity of the application. Desktop Agents MUST validate that the origin of the message matches the URL, but MAY implement custom comparison logic.", + "type": "string", + "format": "uri" + }, + "actualUrl": { + "title": "Actual URL", + "description": "The current URL of the page attempting to connect. This may differ from the identityUrl, but the origins MUST match.", + "type": "string", + "format": "uri" + }, + "instanceId": { + "title": "instanceId", + "description": "If an application has previously connected to the Desktop Agent, it may specify its prior instance id and associated instance UUID to request the same same instance Id be assigned.", + "type": "string" + }, + "instanceUuid": { + "title": "instanceUuid", + "description": "Instance UUID associated with the requested instanceId.", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "identityUrl", "actualUrl" + ] + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityFailedResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityFailedResponse.schema.json new file mode 100644 index 00000000..6a1e1f88 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityFailedResponse.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP5ValidateAppIdentityFailedResponse.schema.json", + "title": "Web Connection Protocol 5 Validate App Identity Failed Response", + "description": "Message sent by the Desktop Agent to an app if their identity validation fails.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP5ValidateAppIdentityFailedResponseBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP5ValidateAppIdentityFailedResponseBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP5ValidateAppIdentityFailedResponse Message Type", + "const": "WCP5ValidateAppIdentityFailedResponse" + }, + "payload": { + "title": "WCP5ValidateAppIdentityFailedResponse Payload", + "type": "object", + "properties": { + "message": { + "title": "Identity Validation failed message", + "type": "string" + } + }, + "additionalProperties": false + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityResponse.schema.json new file mode 100644 index 00000000..60c1da13 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityResponse.schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP5ValidateAppIdentityResponse.schema.json", + "title": "Web Connection Protocol 5 Validate App Identity Success Response", + "description": "Message sent by the Desktop Agent to an app after successful identity validation.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP5ValidateAppIdentityResponseBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP5ValidateAppIdentityResponseBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP5ValidateAppIdentityResponse Message Type", + "const": "WCP5ValidateAppIdentityResponse" + }, + "payload": { + "title": "WCP5ValidateAppIdentityResponse Payload", + "type": "object", + "properties": { + "appId": { + "title": "appId", + "description": "The appId that the app's identity was validated against.", + "type": "string" + }, + "instanceId": { + "title": "instanceId", + "description": "The instance Id granted to the application by the Desktop Agent.", + "type": "string" + }, + "instanceUuid": { + "title": "instanceUuid", + "description": "Instance UUID associated with the instanceId granted, which may be used to retrieve the same instanceId if the app is reloaded or navigates.", + "type": "string" + }, + "implementationMetadata": { + "title": "ImplementationMetadata", + "description": "Implementation metadata for the Desktop Agent, which includes an appMetadata element containing a copy of the app's own metadata.", + "$ref": "api.schema.json#/definitions/ImplementationMetadata" + } + }, + "additionalProperties": false, + "required": [ + "appId", + "instanceId", + "instanceUuid", + "implementationMetadata" + ] + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP6Goodbye.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP6Goodbye.schema.json new file mode 100644 index 00000000..89faa661 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP6Goodbye.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCP6Goodbye.schema.json", + "title": "Web Connection Protocol 6 Goodbye", + "description": "Goodbye message to be sent to the Desktop Agent when disconnecting (e.g. when closing the window or navigating). Desktop Agents should close the MessagePort after receiving this message, but retain instance details in case the application reconnects (e.g. after a navigation event).", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WCP6GoodbyeBase" + }, + { + "$ref": "WCPConnectionStep.schema.json" + } + ], + "$defs": { + "WCP6GoodbyeBase": { + "type": "object", + "properties": { + "type": { + "title": "WCP6Goodbye Message Type", + "const": "WCP6Goodbye" + }, + "meta": { + "$ref": "WCPConnectionStep.schema.json#/$defs/DisconnectStepMeta" + } + }, + "required": [ "type", "meta"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCPConnectionStep.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCPConnectionStep.schema.json new file mode 100644 index 00000000..b5b99171 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WCPConnectionStep.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WCPConnectionStep.schema.json", + "title": "Web Connection Protocol Message", + "type": "object", + "description": "A message used during the connection flow for an application to a Desktop Agent in a browser window. Used for messages sent in either direction.", + "properties": { + "type": { + "title": "Connection Step Message type", + "type": "string", + "enum": [ + "WCP1Hello", + "WCP2LoadUrl", + "WCP3Handshake", + "WCP4ValidateAppIdentity", + "WCP5ValidateAppIdentityFailedResponse", + "WCP5ValidateAppIdentityResponse", + "WCP6Goodbye" + ], + "description": "Identifies the type of the connection step message." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload, containing data pertaining to this connection step.", + "additionalProperties": true + }, + "meta": { + "title": "Connection Step Metadata", + "description": "Metadata for a Web Connection Protocol message.", + "oneOf": [ + { + "$ref": "#/$defs/DisconnectStepMeta" + }, + { + "$ref": "#/$defs/ConnectionStepMeta" + } + ] + } + }, + "required": [ + "type", + "meta" + ], + "additionalProperties": false, + "$defs": { + "ConnectionStepMeta": { + "type": "object", + "properties": { + "connectionAttemptUuid": { + "$ref": "common.schema.json#/$defs/ConnectionAttemptUuid" + }, + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + } + }, + "required": [ + "timestamp", + "connectionAttemptUuid" + ], + "additionalProperties": false + }, + "DisconnectStepMeta": { + "type": "object", + "properties": { + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP1ConnectRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP1ConnectRequest.schema.json new file mode 100644 index 00000000..1f37af14 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP1ConnectRequest.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WSCP1ConnectRequest.schema.json", + "title": "WebSocket Connection Protocol 1 Connect Request", + "description": "Handshake request sent by the party that opens the WebSocket TCP connection.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WSCP1ConnectRequestBase" + }, + { + "$ref": "WSCPConnectionStep.schema.json" + } + ], + "$defs": { + "WSCP1ConnectRequestBase": { + "type": "object", + "properties": { + "type": { + "title": "WSCP1ConnectRequest Message Type", + "const": "WSCP1ConnectRequest" + }, + "payload": { + "title": "WSCP1ConnectRequest Payload", + "type": "object", + "properties": { + "role": { + "title": "Connection role", + "description": "Identifies the initiator's role: application when a native app connects to a DA, desktopAgent when a DA connects to a native app WS server.", + "type": "string", + "enum": ["application", "desktopAgent"] + }, + "protocolVersion": { + "title": "Protocol version", + "description": "WSCP protocol version.", + "type": "string", + "const": "1.0" + }, + "sessionId": { + "title": "Session ID", + "description": "Routes the connection to the correct DA user session.", + "type": "string" + }, + "sharedSecret": { + "title": "Shared secret", + "description": "Per-user, per-app pairing credential for initial connection. MUST be omitted on application-initiated reconnect when instanceUuid is supplied and recognized by the DA. MAY be a single-use launch token when the DA starts the application.", + "type": "string" + }, + "appId": { + "title": "appId", + "description": "Optional app identifier. If provided, the acceptor MUST verify it matches the pairing.", + "type": "string" + }, + "instanceId": { + "title": "instanceId", + "description": "Optional prior instance ID for reconnection.", + "type": "string" + }, + "instanceUuid": { + "title": "instanceUuid", + "description": "Optional DA-assigned instance UUID for reconnection.", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "role", + "protocolVersion", + "sessionId" + ] + }, + "meta": { + "$ref": "WSCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectFailedResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectFailedResponse.schema.json new file mode 100644 index 00000000..4ccef742 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectFailedResponse.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WSCP2ConnectFailedResponse.schema.json", + "title": "WebSocket Connection Protocol 2 Connect Failed Response", + "description": "Message sent by the acceptor if the WSCP handshake fails.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WSCP2ConnectFailedResponseBase" + }, + { + "$ref": "WSCPConnectionStep.schema.json" + } + ], + "$defs": { + "WSCP2ConnectFailedResponseBase": { + "type": "object", + "properties": { + "type": { + "title": "WSCP2ConnectFailedResponse Message Type", + "const": "WSCP2ConnectFailedResponse" + }, + "payload": { + "title": "WSCP2ConnectFailedResponse Payload", + "type": "object", + "properties": { + "message": { + "title": "Connection failed message", + "type": "string" + } + }, + "additionalProperties": false, + "required": ["message"] + }, + "meta": { + "$ref": "WSCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectResponse.schema.json new file mode 100644 index 00000000..741dfceb --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectResponse.schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WSCP2ConnectResponse.schema.json", + "title": "WebSocket Connection Protocol 2 Connect Success Response", + "description": "Message sent by the acceptor after successful WSCP handshake.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WSCP2ConnectResponseBase" + }, + { + "$ref": "WSCPConnectionStep.schema.json" + } + ], + "$defs": { + "WSCP2ConnectResponseBase": { + "type": "object", + "properties": { + "type": { + "title": "WSCP2ConnectResponse Message Type", + "const": "WSCP2ConnectResponse" + }, + "payload": { + "title": "WSCP2ConnectResponse Payload", + "type": "object", + "properties": { + "appId": { + "title": "appId", + "description": "The appId that the connection was validated against.", + "type": "string" + }, + "instanceId": { + "title": "instanceId", + "description": "The instance Id granted to the application by the Desktop Agent.", + "type": "string" + }, + "instanceUuid": { + "title": "instanceUuid", + "description": "Instance UUID associated with the instanceId granted, which may be used to retrieve the same instanceId on reconnection.", + "type": "string" + }, + "implementationMetadata": { + "title": "ImplementationMetadata", + "description": "Implementation metadata for the Desktop Agent.", + "$ref": "api.schema.json#/definitions/ImplementationMetadata" + } + }, + "additionalProperties": false, + "required": [ + "appId", + "instanceId", + "instanceUuid", + "implementationMetadata" + ] + }, + "meta": { + "$ref": "WSCPConnectionStep.schema.json#/$defs/ConnectionStepMeta" + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP3Goodbye.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP3Goodbye.schema.json new file mode 100644 index 00000000..fa2dcf82 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP3Goodbye.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WSCP3Goodbye.schema.json", + "title": "WebSocket Connection Protocol 3 Goodbye", + "description": "Goodbye message to be sent when disconnecting a WebSocket connection. The acceptor should close the connection after receiving this message but retain instance details in case the application reconnects.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/WSCP3GoodbyeBase" + }, + { + "$ref": "WSCPConnectionStep.schema.json" + } + ], + "$defs": { + "WSCP3GoodbyeBase": { + "type": "object", + "properties": { + "type": { + "title": "WSCP3Goodbye Message Type", + "const": "WSCP3Goodbye" + }, + "meta": { + "$ref": "WSCPConnectionStep.schema.json#/$defs/DisconnectStepMeta" + } + }, + "required": ["type", "meta"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCPConnectionStep.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCPConnectionStep.schema.json new file mode 100644 index 00000000..5589b0e2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCPConnectionStep.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/WSCPConnectionStep.schema.json", + "title": "WebSocket Connection Protocol Message", + "type": "object", + "description": "A message used during the WebSocket connection flow between a native application and a Desktop Agent. Used for messages sent in either direction.", + "properties": { + "type": { + "title": "Connection Step Message type", + "type": "string", + "enum": [ + "WSCP1ConnectRequest", + "WSCP2ConnectResponse", + "WSCP2ConnectFailedResponse", + "WSCP3Goodbye" + ], + "description": "Identifies the type of the WebSocket connection step message." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload, containing data pertaining to this connection step.", + "additionalProperties": true + }, + "meta": { + "title": "Connection Step Metadata", + "description": "Metadata for a WebSocket Connection Protocol message.", + "oneOf": [ + { + "$ref": "#/$defs/DisconnectStepMeta" + }, + { + "$ref": "#/$defs/ConnectionStepMeta" + } + ] + } + }, + "required": [ + "type", + "meta" + ], + "additionalProperties": false, + "$defs": { + "ConnectionStepMeta": { + "type": "object", + "properties": { + "connectionAttemptUuid": { + "$ref": "common.schema.json#/$defs/ConnectionAttemptUuid" + }, + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + } + }, + "required": [ + "timestamp", + "connectionAttemptUuid" + ], + "additionalProperties": false + }, + "DisconnectStepMeta": { + "type": "object", + "properties": { + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerRequest.schema.json new file mode 100644 index 00000000..dfbc03ad --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerRequest.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addContextListenerRequest.schema.json", + "type": "object", + "title": "AddContextListener Request", + "description": "A request to add a context listener to a specified Channel OR to the current user channel. Where the listener is added to the current user channel (channelId == null), and this app has already been added to a user channel, client code should make a subsequent request to get the current context of that channel for this listener and then call its handler with it.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddContextListenerRequestType" + }, + "payload": { + "$ref": "#/$defs/AddContextListenerRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddContextListenerRequestType": { + "title": "AddContextListener Request Message Type", + "const": "addContextListenerRequest" + }, + "AddContextListenerRequestPayload": { + "title": "AddContextListener Request Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The id of the channel to add the listener to or `null` indicating that it should listen to the current user channel (at the time of broadcast).", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "contextType": { + "title": "Context type", + "description": "The type of context to listen for OR `null` indicating that it should listen to all context types.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "channelId", "contextType" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerResponse.schema.json new file mode 100644 index 00000000..bd70fd06 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addContextListenerResponse.schema.json", + "type": "object", + "title": "AddContextListener Response", + "description": "A response to a addContextListener request. Where the listener was added to the current user channel (channelId == null), and this app has already been added to a user channel, client code should make a subsequent request to get the current context of that channel for this listener and then call its handler with it.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddContextListenerResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/AddContextListenerSuccessResponsePayload" + }, + { + "$ref": "#/$defs/AddContextListenerErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddContextListenerResponseType": { + "title": "AddContextListener Response Message Type", + "const": "addContextListenerResponse" + }, + "AddContextListenerSuccessResponsePayload": { + "title": "AddContextListener Response Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + }, + "AddContextListenerErrorResponsePayload": { + "title": "AddContextListener Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerRequest.schema.json new file mode 100644 index 00000000..3b06bcc6 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerRequest.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addEventListenerRequest.schema.json", + "type": "object", + "title": "AddEventListener Request", + "description": "A request to add an event listener for a specified event type to the Desktop Agent.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddEventListenerRequestType" + }, + "payload": { + "$ref": "#/$defs/AddEventListenerRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddEventListenerRequestType": { + "title": "AddEventListener Request Message Type", + "const": "addEventListenerRequest" + }, + "AddEventListenerRequestPayload": { + "title": "AddEventListener Request Payload", + "type": "object", + "properties": { + "type": { + "title": "Event type", + "description": "The type of the event to be listened to or `null` to listen to all event types.", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/FDC3EventType" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "type" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerResponse.schema.json new file mode 100644 index 00000000..f7a85363 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addEventListenerResponse.schema.json", + "type": "object", + "title": "AddEventListener Response", + "description": "A response to an addEventListener request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddEventListenerResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/AddEventListenerSuccessResponsePayload" + }, + { + "$ref": "#/$defs/AddEventListenerErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddEventListenerResponseType": { + "title": "AddEventListener Response Message Type", + "const": "addEventListenerResponse" + }, + "AddEventListenerSuccessResponsePayload": { + "title": "AddEventListener Response Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + }, + "AddEventListenerErrorResponsePayload": { + "title": "AddEventListener Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "common.schema.json#/$defs/ErrorMessages" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerRequest.schema.json new file mode 100644 index 00000000..b639d39a --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerRequest.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addIntentListenerRequest.schema.json", + "type": "object", + "title": "AddIntentListener Request", + "description": "A request to add an Intent listener for a specified intent type.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddIntentListenerRequestType" + }, + "payload": { + "$ref": "#/$defs/AddIntentListenerRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddIntentListenerRequestType": { + "title": "AddIntentListener Request Message Type", + "const": "addIntentListenerRequest" + }, + "AddIntentListenerRequestPayload": { + "title": "AddIntentListener Request Payload", + "type": "object", + "properties": { + "intent": { + "title": "Intent name", + "description": "The name of the intent to listen for.", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "intent" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerResponse.schema.json new file mode 100644 index 00000000..1bc9b620 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerResponse.schema.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/addIntentListenerResponse.schema.json", + "type": "object", + "title": "AddIntentListener Response", + "description": "A response to a addIntentListener request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/AddIntentListenerResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/AddIntentListenerSuccessResponsePayload" + }, + { + "$ref": "#/$defs/AddIntentListenerErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "AddIntentListenerResponseType": { + "title": "AddIntentListener Response Message Type", + "const": "addIntentListenerResponse" + }, + "AddIntentListenerSuccessResponsePayload": { + "title": "AddIntentListener Response Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ] + }, + "AddIntentListenerErrorResponsePayload": { + "title": "AddIntentListener Response Error Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ResolveError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentEvent.schema.json new file mode 100644 index 00000000..8cad6559 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentEvent.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/agentEvent.schema.json", + "title": "Agent Event Message", + "type": "object", + "description": "A message from a Desktop Agent to an FDC3-enabled app representing an event.", + "properties": { + "type": { + "title": "Event Message Type", + "type": "string", + "enum": [ + "addEventListenerEvent", + "broadcastEvent", + "channelChangedEvent", + "heartbeatEvent", + "intentEvent", + "privateChannelOnAddContextListenerEvent", + "privateChannelOnDisconnectEvent", + "privateChannelOnUnsubscribeEvent", + "contextClearedEvent" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Event Payload", + "type": "object", + "description": "The message payload contains details of the event that the app is being notified about.", + "additionalProperties": true + }, + "meta": { + "title": "Event Metadata", + "description": "Metadata for messages sent by a Desktop Agent to an app notifying it of an event.", + "type": "object", + "properties": { + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + }, + "eventUuid": { + "$ref": "common.schema.json#/$defs/EventUuid" + } + }, + "required": ["timestamp", "eventUuid"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "payload", "meta"] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentResponse.schema.json new file mode 100644 index 00000000..0e281e1d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/agentResponse.schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/agentResponse.schema.json", + "title": "Agent Response Message", + "type": "object", + "description": "A message from a Desktop Agent to an FDC3-enabled app responding to an API call. If the payload contains an `error` property, the request was unsuccessful.", + "properties": { + "type": { + "title": "Response Message Type", + "type": "string", + "enum": [ + "addContextListenerResponse", + "addEventListenerResponse", + "addIntentListenerResponse", + "broadcastResponse", + "contextListenerUnsubscribeResponse", + "createPrivateChannelResponse", + "eventListenerUnsubscribeResponse", + "findInstancesResponse", + "findIntentResponse", + "findIntentsByContextResponse", + "getAppMetadataResponse", + "getCurrentChannelResponse", + "getCurrentContextResponse", + "getInfoResponse", + "getOrCreateChannelResponse", + "getUserChannelsResponse", + "intentListenerUnsubscribeResponse", + "intentResultResponse", + "joinUserChannelResponse", + "leaveCurrentChannelResponse", + "openResponse", + "privateChannelAddEventListenerResponse", + "privateChannelDisconnectResponse", + "privateChannelUnsubscribeEventListenerResponse", + "raiseIntentForContextResponse", + "raiseIntentResponse", + "raiseIntentResultResponse", + "clearContextResponse" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Response Payload", + "type": "object", + "description": "A payload for a response to an API call that will contain any return values or an `error` property containing a standardized error message indicating that the request was unsuccessful.", + "oneOf": [ + { + "type": "object", + "properties": {}, + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "common.schema.json#/$defs/ErrorMessages" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + ] + }, + "meta": { + "title": "Agent Response Message Metadata", + "description": "Metadata for messages sent by a Desktop Agent to an app in response to an API call.", + "type": "object", + "properties": { + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + }, + "requestUuid": { + "$ref": "common.schema.json#/$defs/ResponseUuid" + }, + "responseUuid": { + "$ref": "common.schema.json#/$defs/ResponseUuid" + }, + "source": { + "title": "Original Source AppIdentifier", + "description": "Field that represents the source application that the request being responded to was received from, for debugging purposes.", + "$ref": "api.schema.json#/definitions/AppIdentifier" + } + }, + "required": [ + "timestamp", + "requestUuid", + "responseUuid" + ], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "type", + "payload", + "meta" + ] +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json new file mode 100644 index 00000000..c5963d27 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json @@ -0,0 +1,714 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/api.schema.json", + "title": "FDC3 Desktop Agent API Schemas", + "definitions": { + "DetachedSignature": { + "type": "object", + "title": "Detached Signature", + "description": "A Detached JSON Web Signature (JWS) proving the authenticity and integrity of signed data. The signature is computed over the canonicalized JSON representation of the data (the payload is not included in the signature structure - it is the data itself). Created using the signing app's private key and verified using the public key from the JWKS URL in the protected header. See the FDC3 Security & Identity documentation for details.", + "properties": { + "protected": { + "type": "string", + "title": "protected", + "description": "The BASE64URL-encoded protected header. When decoded, contains fields including: 'alg' (signature algorithm, e.g., 'EdDSA'), 'jku' (JSON Web Key Set URL for key verification), and 'kid' (key identifier)." + }, + "signature": { + "type": "string", + "title": "signature", + "description": "The BASE64URL-encoded digital signature computed over the protected header and the canonicalized data (detached payload)." + } + }, + "required": [ + "protected", + "signature" + ], + "additionalProperties": false + }, + "AntiReplay": { + "type": "object", + "title": "Anti-Replay Claims", + "description": "Anti-replay claims extracted from the context's antiReplay field after verification.", + "properties": { + "iat": { + "type": "number", + "description": "Issued at time as a Unix timestamp (seconds since epoch)." + }, + "exp": { + "type": "number", + "description": "Expiration time as a Unix timestamp (seconds since epoch)." + }, + "jti": { + "type": "string", + "description": "Unique identifier for this context instance." + } + }, + "required": [ + "iat", + "exp", + "jti" + ], + "additionalProperties": false + }, + "MessageAuthenticity": { + "type": "object", + "title": "Message Authenticity", + "description": "Verification outcomes for signed context objects.", + "properties": { + "signed": { + "type": "boolean", + "description": "Indicates whether the context includes a signature, but check other fields to see if the signature is valid." + }, + "valid": { + "type": "boolean", + "description": "True if the JWS cryptographically verifies against the signed bytes." + }, + "trusted": { + "type": "boolean", + "description": "True if the signing key was obtained from an approved/trusted source." + }, + "alg": { + "type": "string", + "description": "The signature algorithm used (from JWS protected header)." + }, + "kid": { + "type": "string", + "description": "The key identifier used to sign the message (from JWS protected header)." + }, + "jku": { + "type": "string", + "description": "The JSON Web Key Set URL where the public key can be retrieved (from JWS protected header)." + }, + "antiReplayClaims": { + "$ref": "#/definitions/AntiReplay" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Human-readable diagnostics (optional)." + } + }, + "required": [ + "signed" + ], + "additionalProperties": false + }, + "AppIdentifier": { + "description": "Identifies an application, or instance of an application, and is used to target FDC3 API calls, such as `fdc3.open` or `fdc3.raiseIntent` at specific applications or application instances.\n\nWill always include at least an `appId` field, which uniquely identifies a specific app.\n\nIf the `instanceId` field is set then the `AppMetadata` object represents a specific instance of the application that may be addressed using that Id.", + "title": "AppIdentifier", + "type": "object", + "properties": { + "appId": { + "description": "The unique application identifier located within a specific application directory instance. An example of an appId might be 'app@sub.root'.", + "type": "string", + "title": "appId" + }, + "instanceId": { + "description": "An optional instance identifier, indicating that this object represents a specific instance of the application described.", + "type": "string", + "title": "instanceId" + }, + "desktopAgent": { + "description": "The Desktop Agent that the app is available on. Used in Desktop Agent Bridging to identify the Desktop Agent to target.", + "type": "string", + "title": "desktopAgent" + } + }, + "unevaluatedProperties": false, + "required": [ + "appId" + ] + }, + "Icon": { + "description": "Describes an Icon image that may be used to represent the application.", + "title": "Icon", + "type": "object", + "properties": { + "src": { + "description": "The icon url.", + "type": "string", + "title": "src" + }, + "size": { + "description": "The icon dimension, formatted as `x`.", + "type": "string", + "title": "size" + }, + "type": { + "description": "Icon media type. If not present the Desktop Agent may use the src file extension.", + "type": "string", + "title": "type" + } + }, + "additionalProperties": false, + "required": [ + "src" + ] + }, + "Image": { + "description": "Describes an image file, typically a screenshot, that often represents the application in a common usage scenario.", + "title": "Image", + "type": "object", + "properties": { + "src": { + "description": "The image url.", + "type": "string", + "title": "src" + }, + "size": { + "description": "The image dimension, formatted as `x`.", + "type": "string", + "title": "size" + }, + "type": { + "description": "Image media type. If not present the Desktop Agent may use the src file extension.", + "type": "string", + "title": "type" + }, + "label": { + "description": "Caption for the image.", + "type": "string", + "title": "label" + } + }, + "additionalProperties": false, + "required": [ + "src" + ] + }, + "AppMetadata": { + "description": "Extends an `AppIdentifier`, describing an application or instance of an application, with additional descriptive metadata that is usually provided by an FDC3 App Directory that the Desktop Agent connects to.\n\nThe additional information from an app directory can aid in rendering UI elements, such as a launcher menu or resolver UI. This includes a title, description, tooltip and icon and screenshot URLs.\n\nNote that as `AppMetadata` instances are also `AppIdentifiers` they may be passed to the `app` argument of `fdc3.open`, `fdc3.raiseIntent` etc.", + "title": "AppMetadata", + "type": "object", + "properties": { + "name": { + "description": "The 'friendly' app name. \nThis field was used with the `open` and `raiseIntent` calls in FDC3 <2.0, which now require an `AppIdentifier` wth `appId` set. \nNote that for display purposes the `title` field should be used, if set, in preference to this field.", + "type": "string", + "title": "name" + }, + "version": { + "description": "The Version of the application.", + "type": "string", + "title": "version" + }, + "instanceMetadata": { + "description": "An optional set of, implementation specific, metadata fields that can be used to disambiguate instances, such as a window title or screen position. Must only be set if `instanceId` is set.", + "type": "object", + "additionalProperties": true, + "title": "instanceMetadata" + }, + "title": { + "description": "A more user-friendly application title that can be used to render UI elements.", + "type": "string", + "title": "title" + }, + "tooltip": { + "description": "A tooltip for the application that can be used to render UI elements.", + "type": "string", + "title": "tooltip" + }, + "description": { + "description": "A longer, multi-paragraph description for the application that could include markup.", + "type": "string", + "title": "description" + }, + "icons": { + "description": "A list of icon URLs for the application that can be used to render UI elements.", + "type": "array", + "items": { + "$ref": "#/definitions/Icon" + }, + "title": "icons" + }, + "screenshots": { + "description": "Images representing the app in common usage scenarios that can be used to render UI elements.", + "type": "array", + "items": { + "$ref": "#/definitions/Image" + }, + "title": "screenshots" + }, + "resultType": { + "description": "The type of output returned for any intent specified during resolution. May express a particular context type (e.g. \"fdc3.instrument\"), channel (e.g. \"channel\") or a channel that will receive a specified type (e.g. \"channel\").", + "type": [ + "null", + "string" + ], + "title": "resultType" + }, + "appId": { + "description": "The unique application identifier located within a specific application directory instance. An example of an appId might be 'app@sub.root'.", + "type": "string", + "title": "appId" + }, + "instanceId": { + "description": "An optional instance identifier, indicating that this object represents a specific instance of the application described.", + "type": "string", + "title": "instanceId" + }, + "desktopAgent": { + "description": "The Desktop Agent that the app is available on. Used in Desktop Agent Bridging to identify the Desktop Agent to target.", + "type": "string", + "title": "desktopAgent" + } + }, + "additionalProperties": false, + "required": [ + "appId" + ] + }, + "IntentMetadata": { + "description": "Metadata describing an Intent.", + "type": "object", + "properties": { + "name": { + "description": "The unique name of the intent that can be invoked by the raiseIntent call.", + "type": "string", + "title": "name" + }, + "displayName": { + "description": "Display name for the intent.", + "type": "string", + "title": "displayName" + } + }, + "additionalProperties": false, + "required": [ + "name" + ] + }, + "AppIntent": { + "description": "An interface that relates an intent to apps.", + "title": "AppIntent", + "type": "object", + "properties": { + "intent": { + "$ref": "#/definitions/IntentMetadata", + "description": "Details of the intent whose relationship to resolving applications is being described.", + "title": "intent" + }, + "apps": { + "description": "Details of applications that can resolve the intent.", + "type": "array", + "items": { + "$ref": "#/definitions/AppMetadata" + }, + "title": "apps" + } + }, + "additionalProperties": false, + "required": [ + "apps", + "intent" + ] + }, + "DisplayMetadata": { + "description": "A system channel will be global enough to have a presence across many apps. This gives us some hints\nto render them in a standard way. It is assumed it may have other properties too, but if it has these,\nthis is their meaning.", + "title": "DisplayMetadata", + "type": "object", + "properties": { + "name": { + "description": "A user-readable name for this channel, e.g: `\"Red\"`.", + "type": "string", + "title": "name" + }, + "color": { + "description": "The color that should be associated within this channel when displaying this channel in a UI, e.g: `0xFF0000`.", + "type": "string", + "title": "color" + }, + "glyph": { + "description": "A URL of an image that can be used to display this channel.", + "type": "string", + "title": "glyph" + } + }, + "additionalProperties": false + }, + "Channel": { + "description": "Represents a context channel that applications can use to send and receive\ncontext data.\n\nPlease note that There are differences in behavior when you interact with a\nUser channel via the `DesktopAgent` interface and the `Channel` interface.\nSpecifically, when 'joining' a User channel or adding a context listener\nwhen already joined to a channel via the `DesktopAgent` interface, existing\ncontext (matching the type of the context listener) on the channel is\nreceived by the context listener immediately. Whereas, when a context\nlistener is added via the Channel interface, context is not received\nautomatically, but may be retrieved manually via the `getCurrentContext()`\nfunction.", + "title": "Channel", + "type": "object", + "properties": { + "id": { + "description": "Constant that uniquely identifies this channel.", + "type": "string", + "title": "id" + }, + "type": { + "description": "Uniquely defines each channel type.\nCan be \"user\", \"app\" or \"private\".", + "enum": [ + "app", + "private", + "user" + ], + "type": "string", + "title": "type" + }, + "displayMetadata": { + "description": "Channels may be visualized and selectable by users. DisplayMetadata may be used to provide hints on how to see them.\nFor App channels, displayMetadata would typically not be present.", + "$ref": "#/definitions/DisplayMetadata", + "title": "displayMetadata" + } + }, + "additionalProperties": false, + "required": [ + "id", + "type" + ] + }, + "ContextMetadata": { + "type": "object", + "description": "Metadata relating to a broadcastEvent or intentEvent, which may include metadata provided by the Desktop Agent or the App that initiated the broadcast, raise intent or open request.", + "title": "Context Metadata", + "properties": { + "source": { + "$ref": "#/definitions/AppIdentifier", + "description": "Identifier for the app instance that sent the context and/or intent.", + "title": "source" + }, + "traceId": { + "type": "string", + "title": "traceId", + "description": "A unique identifier for the context or intent that can be used to trace the context or intent through the system." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "timestamp", + "description": "The timestamp when the context or intent was created, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator." + }, + "custom": { + "type": "object", + "additionalProperties": true, + "title": "custom", + "description": "Custom metadata that can be used to provide additional information about the context or intent. This allows for individuals to use metadata fields that have yet to be standardized." + }, + "signature": { + "$ref": "#/definitions/DetachedSignature", + "description": "A Detached JSON Web Signature (JWS) proving the authenticity and integrity of the context." + }, + "authenticity": { + "$ref": "#/definitions/MessageAuthenticity", + "description": "The result of verifying the context's signature, populated by the receiving app's security layer after attempting signature verification." + }, + "antiReplay": { + "$ref": "#/definitions/AntiReplay", + "description": "Anti-replay claims supplied with signed context (e.g. merged from intentResultRequest metadata into resultMetadata)." + } + }, + "required": [ + "source", + "timestamp", + "traceId" + ], + "additionalProperties": false + }, + "AppProvidableContextMetadata": { + "type": "object", + "description": "Metadata that can be provided by an app as part of a broadcast, raise intent or open API call.", + "title": "App Providable Context Metadata", + "properties": { + "traceId": { + "$ref": "#/definitions/ContextMetadata/properties/traceId" + }, + "custom": { + "$ref": "#/definitions/ContextMetadata/properties/custom" + }, + "signature": { + "$ref": "#/definitions/DetachedSignature", + "description": "A Detached JSON Web Signature (JWS) proving the authenticity and integrity of the context." + }, + "antiReplay": { + "description": "Should be populated when the context is signed. Included in the signature. Prevents replay attacks where context objects are re-used.", + "$ref": "#/definitions/AntiReplay" + } + }, + "additionalProperties": false + }, + "DesktopAgentIdentifier": { + "description": "Identifies a particular Desktop Agent in Desktop Agent Bridging scenarios\nwhere a request needs to be directed to a Desktop Agent rather than a specific app, or a\nresponse message is returned by the Desktop Agent (or more specifically its resolver)\nrather than a specific app. Used as a substitute for `AppIdentifier` in cases where no\napp details are available or are appropriate.", + "title": "DesktopAgentIdentifier", + "type": "object", + "properties": { + "desktopAgent": { + "description": "Used in Desktop Agent Bridging to attribute or target a message to a\nparticular Desktop Agent.", + "type": "string", + "title": "desktopAgent" + } + }, + "unevaluatedProperties": false, + "required": [ + "desktopAgent" + ] + }, + "OpenError": { + "description": "Constants representing the errors that can be encountered when calling the `open` method on the DesktopAgent object (`fdc3`).", + "title": "OpenError", + "enum": [ + "AppNotFound", + "AppTimeout", + "DesktopAgentNotFound", + "ErrorOnLaunch", + "MalformedContext", + "ResolverUnavailable", + "ApiTimeout", + "InvalidArguments" + ], + "type": "string" + }, + "ResolveError": { + "description": "Constants representing the errors that can be encountered when calling the `addIntentListener`, `findIntent`, `findIntentsByContext`, `raiseIntent` or `raiseIntentForContext` methods on the DesktopAgent (`fdc3`).", + "title": "ResolveError", + "enum": [ + "DesktopAgentNotFound", + "IntentDeliveryFailed", + "MalformedContext", + "NoAppsFound", + "ResolverTimeout", + "ResolverUnavailable", + "TargetAppUnavailable", + "TargetInstanceUnavailable", + "UserCancelledResolution", + "ApiTimeout", + "InvalidArguments", + "IntentListenerConflict" + ], + "type": "string" + }, + "ResultError": { + "title": "ResultError", + "enum": [ + "IntentHandlerRejected", + "NoResultReturned", + "ApiTimeout" + ], + "type": "string" + }, + "ChannelError": { + "title": "ChannelError", + "enum": [ + "AccessDenied", + "CreationFailed", + "MalformedContext", + "NoChannelFound", + "ApiTimeout", + "InvalidArguments" + ], + "type": "string" + }, + "BridgingError": { + "title": "BridgingError", + "enum": [ + "AgentDisconnected", + "NotConnectedToBridge", + "ResponseToBridgeTimedOut", + "MalformedMessage" + ], + "type": "string" + }, + "BaseImplementationMetadata": { + "description": "Metadata relating to the FDC3 Desktop Agent implementation and its provider.", + "title": "BaseImplementationMetadata", + "type": "object", + "properties": { + "fdc3Version": { + "description": "The version number of the FDC3 specification that the implementation provides.\nThe string must be a numeric semver version, e.g. 1.2 or 1.2.1.", + "type": "string", + "title": "fdc3Version" + }, + "provider": { + "description": "The name of the provider of the Desktop Agent implementation (e.g. Finsemble, Glue42, OpenFin etc.).", + "type": "string", + "title": "provider" + }, + "providerVersion": { + "description": "The version of the provider of the Desktop Agent implementation (e.g. 5.3.0).", + "type": "string", + "title": "providerVersion" + }, + "optionalFeatures": { + "description": "Metadata indicating whether the Desktop Agent implements optional features of\nthe Desktop Agent API.", + "type": "object", + "properties": { + "UserChannelMembershipAPIs": { + "description": "Used to indicate whether the optional `fdc3.joinUserChannel`,\n`fdc3.getCurrentChannel` and `fdc3.leaveCurrentChannel` are implemented by\nthe Desktop Agent.", + "type": "boolean", + "title": "UserChannelMembershipAPIs" + }, + "DesktopAgentBridging": { + "description": "Used to indicate whether the experimental Desktop Agent Bridging\nfeature is implemented by the Desktop Agent.", + "type": "boolean", + "title": "DesktopAgentBridging" + } + }, + "additionalProperties": false, + "required": [ + "DesktopAgentBridging", + "UserChannelMembershipAPIs" + ], + "title": "optionalFeatures" + } + }, + "required": [ + "fdc3Version", + "optionalFeatures", + "provider" + ] + }, + "ImplementationMetadata": { + "description": "Includes Metadata for the current application.", + "title": "ImplementationMetadata", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/BaseImplementationMetadata" + }, + { + "type": "object", + "properties": { + "appMetadata": { + "$ref": "#/definitions/AppMetadata", + "description": "The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`).", + "title": "appMetadata" + } + } + } + ], + "properties": { + "fdc3Version": true, + "provider": true, + "providerVersion": true, + "optionalFeatures": true, + "appMetadata": true + }, + "required": [ + "fdc3Version", + "optionalFeatures", + "provider", + "appMetadata" + ], + "additionalProperties": false + }, + "IntentResolution": { + "description": "IntentResolution provides a standard format for data returned upon resolving an intent.\n\n```javascript\n//resolve a \"Chain\" type intent\nlet resolution = await agent.raiseIntent(\"intentName\", context);\n\n//resolve a \"Client-Service\" type intent with a data response or a Channel\nlet resolution = await agent.raiseIntent(\"intentName\", context);\ntry {\n\t const result = await resolution.getResult();\n if (result && result.broadcast) {\n console.log(`${resolution.source} returned a channel with id ${result.id}`);\n } else if (result){\n console.log(`${resolution.source} returned data: ${JSON.stringify(result)}`);\n } else {\n console.error(`${resolution.source} didn't return data`\n }\n} catch(error) {\n console.error(`${resolution.source} returned an error: ${error}`);\n}\n\n// Use metadata about the resolving app instance to target a further intent\nawait agent.raiseIntent(\"intentName\", context, resolution.source);\n```", + "title": "IntentResolution", + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/AppIdentifier", + "description": "Identifier for the app instance that was selected (or started) to resolve the intent.\n`source.instanceId` MUST be set, indicating the specific app instance that\nreceived the intent.", + "title": "source" + }, + "intent": { + "description": "The intent that was raised. May be used to determine which intent the user\nchose in response to `fdc3.raiseIntentForContext()`.", + "type": "string", + "title": "intent" + } + }, + "additionalProperties": false, + "required": [ + "intent", + "source" + ] + }, + "IntentResult": { + "title": "IntentResult", + "anyOf": [ + { + "type": "object", + "title": "IntentResult Context", + "properties": { + "context": { + "$ref": "../context/context.schema.json" + } + }, + "required": [ + "context" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "IntentResult Channel", + "properties": { + "channel": { + "$ref": "#/definitions/Channel" + } + }, + "required": [ + "channel" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "IntentResult Void", + "properties": {}, + "additionalProperties": false + } + ] + }, + "FDC3EventType": { + "title": "FDC3 Event Type", + "description": "The type of a (non-context and non-intent) event that may be received via the FDC3 API's addEventListener function.", + "type": "string", + "enum": [ + "USER_CHANNEL_CHANGED" + ] + }, + "FDC3Event": { + "title": "FDC3 Event", + "description": "An event object received via the FDC3 API's addEventListener function. Will always include both type and details, which describe type of the event and any additional details respectively.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/FDC3EventType" + }, + "details": { + "title": "Event details", + "description": "Additional details of the event, such as the `currentChannelId` for a CHANNEL_CHANGED event.", + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "type", + "details" + ], + "additionalProperties": false + }, + "PrivateChannelEventType": { + "title": "PrivateChannel Event Type", + "description": "Type defining valid type strings for Private Channel events.", + "type": "string", + "enum": [ + "addContextListener", + "unsubscribe", + "disconnect" + ] + }, + "PrivateChannelEvent": { + "description": "Type defining the format of event objects that may be received via a PrivateChannel's addEventListener function.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/PrivateChannelEventType" + }, + "details": { + "title": "Event details", + "description": "Additional details of the event, such as the `currentChannelId` for a CHANNEL_CHANGED event.", + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "type", + "details" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/appRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/appRequest.schema.json new file mode 100644 index 00000000..1ed93e10 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/appRequest.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/appRequest.schema.json", + "title": "App Request Message", + "type": "object", + "description": "A request message from an FDC3-enabled app to a Desktop Agent.", + "properties": { + "type": { + "title": "Request Message type", + "type": "string", + "enum": [ + "addContextListenerRequest", + "addEventListenerRequest", + "addIntentListenerRequest", + "broadcastRequest", + "contextListenerUnsubscribeRequest", + "createPrivateChannelRequest", + "eventListenerUnsubscribeRequest", + "findInstancesRequest", + "findIntentRequest", + "findIntentsByContextRequest", + "getAppMetadataRequest", + "getCurrentChannelRequest", + "getCurrentContextRequest", + "getInfoRequest", + "getOrCreateChannelRequest", + "getUserChannelsRequest", + "heartbeatAcknowledgementRequest", + "intentListenerUnsubscribeRequest", + "intentResultRequest", + "joinUserChannelRequest", + "leaveCurrentChannelRequest", + "openRequest", + "privateChannelAddEventListenerRequest", + "privateChannelDisconnectRequest", + "privateChannelUnsubscribeEventListenerRequest", + "raiseIntentForContextRequest", + "raiseIntentRequest", + "clearContextRequest" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Request' appended." + }, + "payload": { + "title": "Request payload", + "type": "object", + "description": "The message payload typically contains the arguments to FDC3 API functions." + }, + "meta": { + "title": "Request Metadata", + "description": "Metadata for a request message sent by an FDC3-enabled app to a Desktop Agent.", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "common.schema.json#/$defs/RequestUuid" + }, + "timestamp": { + "$ref": "common.schema.json#/$defs/Timestamp" + }, + "source": { + "title": "Source AppIdentifier", + "description": "Field that represents the source application that a request or response was received from. Please note that this may be set by an app or Desktop Agent proxy for debugging purposes but a Desktop Agent should make its own determination of the source of a message to avoid spoofing.", + "$ref": "api.schema.json#/definitions/AppIdentifier" + } + }, + "required": ["requestUuid", "timestamp"], + "additionalProperties": false + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastEvent.schema.json new file mode 100644 index 00000000..054a59c5 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastEvent.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/broadcastEvent.schema.json", + "type": "object", + "title": "broadcast Event", + "description": "An event message from the Desktop Agent to an app indicating that context has been broadcast on a channel it is listening to, or specifically to this app instance if it was launched via `fdc3.open` and context was passed.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/BroadcastEventType" + }, + "payload": { + "$ref": "#/$defs/BroadcastEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "BroadcastEventType": { + "title": "Broadcast Event Message Type", + "const": "broadcastEvent" + }, + "BroadcastEventPayload": { + "title": "broadcast Event Payload", + "type": "object", + "properties": { + "channelId": { + "title": "channel Id", + "description": "The Id of the channel that the broadcast was sent on. May be `null` if the context is being broadcast due to a call `fdc3.open` that passed context.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "context": { + "$ref": "../context/context.schema.json", + "title": "Context", + "description": "The context object that was broadcast." + }, + "metadata": { + "$ref": "api.schema.json#/definitions/ContextMetadata" + } + }, + "additionalProperties": false, + "required": [ + "channelId", "context", "metadata" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastRequest.schema.json new file mode 100644 index 00000000..2ba4878f --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastRequest.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/broadcastRequest.schema.json", + "type": "object", + "title": "Broadcast Request", + "description": "A request to broadcast context on a channel.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/BroadcastRequestType" + }, + "payload": { + "$ref": "#/$defs/BroadcastRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "BroadcastRequestType": { + "title": "Broadcast Request Message Type", + "const": "broadcastRequest" + }, + "BroadcastRequestPayload": { + "title": "broadcast Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The Id of the Channel that the broadcast was sent on." + }, + "context": { + "$ref": "../context/context.schema.json", + "title": "Context", + "description": "The context object that is to be broadcast." + }, + "metadata": { + "$ref": "api.schema.json#/definitions/AppProvidableContextMetadata" + } + }, + "additionalProperties": false, + "required": [ + "channelId", + "context", + "metadata" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastResponse.schema.json new file mode 100644 index 00000000..6b6a3858 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/broadcastResponse.schema.json", + "type": "object", + "title": "Broadcast Response", + "description": "A response to a request to broadcast context on a channel.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/BroadcastResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "BroadcastResponseType": { + "title": "Broadcast Response Message Type", + "const": "broadcastResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/channelChangedEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/channelChangedEvent.schema.json new file mode 100644 index 00000000..3b7284c9 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/channelChangedEvent.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/channelChangedEvent.schema.json", + "type": "object", + "title": "channelChanged Event", + "description": "An event message from the Desktop Agent to an app indicating that its current user channel has changed.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ChannelChangedEventType" + }, + "payload": { + "$ref": "#/$defs/ChannelChangedEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ChannelChangedEventType": { + "title": "ChannelChanged Event Message Type", + "const": "channelChangedEvent" + }, + "ChannelChangedEventPayload": { + "title": "channelChanged Event Payload", + "type": "object", + "anyOf": [ + { + "deprecated": true, + "properties": { + "newChannelId": { + "title": "New Channel Id", + "description": "Deprecated - allowed for backwards compatibility. The Id of the channel that the app was added to or `null` if it was removed from a channel.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "newChannelId" + ] + },{ + "properties": { + "currentChannelId": { + "title": "Current Channel Id", + "description": "The Id of the channel that the app was added to or `null` if it was removed from a channel.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "currentChannelId" + ] + } + ] + + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextRequest.schema.json new file mode 100644 index 00000000..8f571b18 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextRequest.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/clearContextRequest.schema.json", + "type": "object", + "title": "Clear Context Request", + "description": "A request to clear context on a channel.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ClearContextRequestType" + }, + "payload": { + "$ref": "#/$defs/ClearContextRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ClearContextRequestType": { + "title": "Clear Context Request Message Type", + "const": "clearContextRequest" + }, + "ClearContextRequestPayload": { + "title": "Clear Context Request Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The id of the channel to clear the context on.", + "type": "string" + }, + "contextType": { + "title": "Context type", + "description": "The type of context to clear for OR `null` indicating that all context types on the channel should be cleared.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["channelId", "contextType"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextResponse.schema.json new file mode 100644 index 00000000..4f7ebf41 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/clearContextResponse.schema.json", + "type": "object", + "title": "Clear Context Response", + "description": "A response to a request to clear context on a channel.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ClearContextResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ClearContextResponseType": { + "title": "Clear Context Response Message Type", + "const": "clearContextResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/common.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/common.schema.json new file mode 100644 index 00000000..6c35a470 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/common.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/common.schema.json", + "title": "Common definitions", + "type": "object", + "description": "Common definitions that are referenced in the API and Bridging Wire Protocol schemas.", + "$defs": { + "ConnectionAttemptUuid": { + "title": "Connection Attempt UUID", + "type": "string", + "description": "Unique identifier for a for an attempt to connect to a Desktop Agent. A Unique UUID should be used in the first (WCP1Hello) message and should be quoted in all subsequent messages to link them to the same connection attempt." + }, + "RequestUuid": { + "title": "Request UUID", + "type": "string", + "description": "Unique identifier for a request or event message. Required in all message types." + }, + "ResponseUuid": { + "title": "Response UUID", + "type": "string", + "description": "Unique identifier for a response to a specific message and must always be accompanied by a RequestUuid." + }, + "EventUuid": { + "title": "Event UUID", + "type": "string", + "description": "Unique identifier for an event message sent from a Desktop Agent to an app." + }, + "ListenerUuid": { + "title": "Listener UUID", + "type": "string", + "description": "Unique identifier for a `listener` object returned by a Desktop Agent to an app in response to addContextListener, addIntentListener or one of the PrivateChannel event listeners and used to identify it in messages (e.g. when unsubscribing)." + }, + "Timestamp": { + "title": "Timestamp", + "type": "string", + "format": "date-time", + "description": "Timestamp at which the message was generated." + }, + "ErrorMessages": { + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ChannelError" + }, + { + "$ref": "api.schema.json#/definitions/OpenError" + }, + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/ResultError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextClearedEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextClearedEvent.schema.json new file mode 100644 index 00000000..69d26faf --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextClearedEvent.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/contextClearedEvent.schema.json", + "type": "object", + "title": "contextCleared Event", + "description": "An event message from the Desktop Agent to an app indicating that context has been cleared on a channel.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ContextClearedEventType" + }, + "payload": { + "$ref": "#/$defs/ContextClearedEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ContextClearedEventType": { + "title": "ContextCleared Event Message Type", + "const": "contextClearedEvent" + }, + "ContextClearedEventPayload": { + "title": "contextCleared Event Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The Id of the channel that was cleared.", + "type": ["string", "null"] + }, + "contextType": { + "title": "Context Type", + "description": "The type of context that was cleared, or null if all types were cleared.", + "type": ["string", "null"] + } + }, + "required": ["channelId", "contextType"], + "additionalProperties": false + } +} +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeRequest.schema.json new file mode 100644 index 00000000..d0f1c3ab --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/contextListenerUnsubscribeRequest.schema.json", + "type": "object", + "title": "ContextListenerUnsubscribe Request", + "description": "A request to unsubscribe a context listener.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ContextListenerUnsubscribeRequestType" + }, + "payload": { + "$ref": "#/$defs/ContextListenerUnsubscribeRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ContextListenerUnsubscribeRequestType": { + "title": "ContextListenerUnsubscribe Request Message Type", + "const": "contextListenerUnsubscribeRequest" + }, + "ContextListenerUnsubscribeRequestPayload": { + "title": "ContextListenerUnsubscribe Request Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeResponse.schema.json new file mode 100644 index 00000000..c9bd341e --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/contextListenerUnsubscribeResponse.schema.json", + "type": "object", + "title": "ContextListenerUnsubscribe Response", + "description": "A response to a contextListenerUnsubscribe request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/ContextListenerUnsubscribeResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "ContextListenerUnsubscribeResponseType": { + "title": "ContextListenerUnsubscribe Response Message Type", + "const": "contextListenerUnsubscribeResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelRequest.schema.json new file mode 100644 index 00000000..f64a9c6b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/createPrivateChannelRequest.schema.json", + "type": "object", + "title": "CreatePrivateChannel Request", + "description": "Request to return a Channel with an auto-generated identity that is intended for private communication between applications.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/CreatePrivateChannelRequestType" + }, + "payload": { + "$ref": "#/$defs/CreatePrivateChannelRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "CreatePrivateChannelRequestType": { + "title": "CreatePrivateChannel Request Message Type", + "const": "createPrivateChannelRequest" + }, + "CreatePrivateChannelRequestPayload": { + "title": "CreatePrivateChannel Request Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelResponse.schema.json new file mode 100644 index 00000000..357538c5 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/createPrivateChannelResponse.schema.json", + "type": "object", + "title": "CreatePrivateChannel Response", + "description": "A response to a createPrivateChannel request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/CreatePrivateChannelResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/CreatePrivateChannelSuccessResponsePayload" + }, + { + "$ref": "#/$defs/CreatePrivateChannelErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "CreatePrivateChannelResponseType": { + "title": "CreatePrivateChannel Response Message Type", + "const": "createPrivateChannelResponse" + }, + "CreatePrivateChannelSuccessResponsePayload": { + "title": "CreatePrivateChannel Response Payload", + "type": "object", + "properties": { + "privateChannel": { + "$ref": "api.schema.json#/definitions/Channel" + } + }, + "required": [ + "privateChannel" + ], + "additionalProperties": false + }, + "CreatePrivateChannelErrorResponsePayload": { + "title": "CreatePrivateChannel Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeRequest.schema.json new file mode 100644 index 00000000..b3569b25 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/eventListenerUnsubscribeRequest.schema.json", + "type": "object", + "title": "EventListenerUnsubscribe Request", + "description": "A request to unsubscribe an event listener.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/EventListenerUnsubscribeRequestType" + }, + "payload": { + "$ref": "#/$defs/EventListenerUnsubscribeRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "EventListenerUnsubscribeRequestType": { + "title": "EventListenerUnsubscribe Request Message Type", + "const": "eventListenerUnsubscribeRequest" + }, + "EventListenerUnsubscribeRequestPayload": { + "title": "EventListenerUnsubscribe Request Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeResponse.schema.json new file mode 100644 index 00000000..465bf9d2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/eventListenerUnsubscribeResponse.schema.json", + "type": "object", + "title": "EventListenerUnsubscribe Response", + "description": "A response to an eventListenerUnsubscribe request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/EventListenerUnsubscribeResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "EventListenerUnsubscribeResponseType": { + "title": "EventListenerUnsubscribe Response Message Type", + "const": "eventListenerUnsubscribeResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannelSelected.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannelSelected.schema.json new file mode 100644 index 00000000..8b116513 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannelSelected.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceChannelSelected.schema.json", + "title": "Fdc3 UserInterface Channel Selected", + "description": "Message from a channel selector UI to the DA proxy sent when the channel selection changes.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceChannelSelectedBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceChannelSelectedBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface ChannelSelected Message Type", + "const": "Fdc3UserInterfaceChannelSelected" + }, + "payload": { + "title": "Fdc3 UserInterface ChannelSelected Payload", + "type": "object", + "properties": { + "selected": { + "title": "Selected Channel", + "description": "The id of the channel that should be currently selected, or `null` if none should be selected.", + "oneOf": [ + {"type": "string"}, {"type": "null"} + ] + } + }, + "additionalProperties": false, + "required": ["selected"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} + diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannels.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannels.schema.json new file mode 100644 index 00000000..c76936f1 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannels.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceChannels.schema.json", + "title": "Fdc3 UserInterface Channels", + "description": "Setup message sent by the DA proxy code in getAgent() to a channel selector UI in an iframe with the channel definitions and current channel selection.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceChannelsBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceChannelsBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Channels Message Type", + "const": "Fdc3UserInterfaceChannels" + }, + "payload": { + "title": "Fdc3 UserInterface Channels Payload", + "type": "object", + "properties": { + "userChannels": { + "title": "User Channels", + "description": "User Channel definitions.```````s", + "type": "array", + "items": { + "$ref": "api.schema.json#/definitions/Channel" + } + }, + "selected": { + "title": "Selected Channel", + "description": "The id of the channel that should be currently selected, or `null` if none should be selected.", + "oneOf": [ + {"type": "string"}, {"type": "null"} + ] + } + }, + "additionalProperties": false, + "required": ["userChannels", "selected"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} + diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceDrag.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceDrag.schema.json new file mode 100644 index 00000000..390dea55 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceDrag.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceDrag.schema.json", + "title": "Fdc3 UserInterface Drag", + "description": "Message from a UI iframe to the DA proxy (setup by `getAgent()`) indicating that the user is dragging the UI to a new location and providing the offset to apply to the location. The DA proxy implementation should limit the location to the current bounds of the window's viewport.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceDragBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceDragBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Drag Message Type", + "const": "Fdc3UserInterfaceDrag" + }, + "payload": { + "title": "Fdc3 UserInterface Drag Payload", + "type": "object", + "properties": { + "mouseOffsets": { + "title": "Mouse Offsets", + "description": "The offset to move the frame by.", + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } + }, + "required": ["x", "y"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["mouseOffsets"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} + diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHandshake.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHandshake.schema.json new file mode 100644 index 00000000..29fa7415 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHandshake.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceHandshake.schema.json", + "title": "Fdc3 UserInterface Handshake", + "description": "Handshake message sent back to a user interface from the DA proxy code (setup by `getAgent()`) over the `MessagePort` provided in the preceding Fdc3UserInterfaceHello message, confirming that it is listening to the `MessagePort` for further communication.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceHandshakeBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceHandshakeBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Handshake Message Type", + "const": "Fdc3UserInterfaceHandshake" + }, + "payload": { + "title": "Fdc3 UserInterface Handshake Payload", + "type": "object", + "properties": { + "fdc3Version": { + "title": "FDC3 version", + "type": "string", + "description": "The version of FDC3 API that the Desktop Agent will provide support for." + } + }, + "additionalProperties": false, + "required": ["fdc3Version"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHello.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHello.schema.json new file mode 100644 index 00000000..a920bf80 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHello.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceHello.schema.json", + "title": "Fdc3 UserInterface Hello", + "description": "Hello message sent by a UI to the Desktop Agent proxy setup by `getAgent()` to indicate it is ready to communicate, containing initial CSS to set on the iframe, and including an appended `MessagePort` to be used for further communication.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceHelloBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceHelloBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Hello Message Type", + "const": "Fdc3UserInterfaceHello" + }, + "payload": { + "title": "Fdc3 UserInterface Hello Payload", + "type": "object", + "properties": { + "implementationDetails": { + "title": "Implementation Details", + "type": "string", + "description": "Details about the UI implementation, such as vendor and version, for logging purposes." + }, + "initialCSS": { + "title": "Initial CSS", + "type": "object", + "description": "A constrained set of styling properties that should be set on the user interface before it is displayed. Note `position` cannot be specified and should always be set to `fixed`.", + "properties": { + "height": {"type": "string", "title": "height", "description": "The initial height of the iframe."}, + "width": {"type": "string", "title": "width", "description": "The initial width of the iframe."}, + "zIndex": {"type": "string", "title": "zIndex", "description": "The initial zindex to apply to the iframe."}, + "left": {"type": "string", "title": "left", "description": "The initial left property to apply to the iframe."}, + "top": {"type": "string", "title": "top", "description": "The initial top property to apply to the iframe."}, + "bottom": {"type": "string", "title": "bottom", "description": "The initial bottom property to apply to the iframe."}, + "right": {"type": "string", "title": "right", "description": "The initial right property to apply to the iframe."}, + "transition": {"type": "string", "title": "transition", "description": "The transition property to apply to the iframe."}, + "maxHeight": {"type": "string", "title": "maxHeight", "description": "The maximum height to apply to the iframe."}, + "maxWidth": {"type": "string", "title": "maxWidth", "description": "The maximum with to apply to the iframe."} + }, + "required": [] + } + }, + "additionalProperties": false, + "required": ["implementationDetails","initialCSS"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceMessage.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceMessage.schema.json new file mode 100644 index 00000000..a922c6c2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceMessage.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceMessage.schema.json", + "title": "Fdc3 UserInterface Message", + "type": "object", + "description": "A message used to communicate with user interface frames injected by `getAgent()` for displaying UI elements such as the intent resolver or channel selector. Used for messages sent in either direction.", + "properties": { + "type": { + "title": "Fdc3 UserInterface Message type", + "type": "string", + "enum": [ + "Fdc3UserInterfaceHello", + "Fdc3UserInterfaceHandshake", + "Fdc3UserInterfaceRestyle", + "Fdc3UserInterfaceDrag", + "Fdc3UserInterfaceResolve", + "Fdc3UserInterfaceResolveAction", + "Fdc3UserInterfaceChannels", + "Fdc3UserInterfaceChannelSelected" + ], + "description": "Identifies the type of the message to or from the user interface frame." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload.", + "additionalProperties": true + } + }, + "required": [ + "type" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolve.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolve.schema.json new file mode 100644 index 00000000..1a30076b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolve.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceResolve.schema.json", + "title": "Fdc3 UserInterface Resolve", + "description": "Setup message sent by the DA proxy code in getAgent() to an intent resolver UI with the resolver data to setup the UI.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceResolveBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceResolveBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Resolve Message Type", + "const": "Fdc3UserInterfaceResolve" + }, + "payload": { + "title": "Fdc3 UserInterface Resolve Payload", + "type": "object", + "properties": { + "context": { + "$ref": "../context/context.schema.json", + "title": "Context" + }, + "appIntents": { + "title": "Resolution options", + "type": "array", + "description": "An array of AppIntent objects defining the resolution options.", + "items": { + "$ref": "api.schema.json#/definitions/AppIntent" + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["context", "appIntents"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} + diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolveAction.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolveAction.schema.json new file mode 100644 index 00000000..78169097 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolveAction.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceResolveAction.schema.json", + "title": "Fdc3 UserInterface Resolve Action", + "description": "Message from an intent resolver UI to DA proxy code in getAgent() reporting a user action.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceResolveActionBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceResolveActionBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface ResolveAction Message Type", + "const": "Fdc3UserInterfaceResolveAction" + }, + "payload": { + "oneOf": [ + { "$ref": "#/$defs/Fdc3UserInterfaceResolveActionPayload"}, + { "$ref": "#/$defs/Fdc3UserInterfaceResolveCancelPayload"} + ] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "Fdc3UserInterfaceResolveActionPayload": { + "title": "Fdc3 UserInterface Resolve Action Payload", + "type": "object", + "properties": { + "intent": { + "title": "Intent name", + "type": "string", + "description": "The intent resolved." + }, + "appIdentifier": { + "title": "AppIdentifier", + "description": "The App resolution option chosen.", + "$ref": "api.schema.json#/definitions/AppIdentifier" + }, + "action": { + "oneOf": [ + { + "type": "string", + "const": "hover" + }, + { + "type": "string", + "const": "click" + } + ] + } + }, + "required": [ + "intent", + "appIdentifier", + "action" + ], + "additionalProperties": false + }, + "Fdc3UserInterfaceResolveCancelPayload": { + "title": "Fdc3 UserInterface Resolve Cancel Payload", + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "cancel" + } + }, + "required": [ + "action" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceRestyle.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceRestyle.schema.json new file mode 100644 index 00000000..16e4002d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceRestyle.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/fdc3UserInterfaceRestyle.schema.json", + "title": "Fdc3 UserInterface Restyle", + "description": "Message from a UI frame to the DA proxy code (setup by `getAgent()`) with updated styling information to apply to it. Can be used to implement a pop-open or close interaction or other transition needed by a UI implementation.", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Fdc3UserInterfaceRestyleBase" + }, + { + "$ref": "fdc3UserInterfaceMessage.schema.json" + } + ], + "$defs": { + "Fdc3UserInterfaceRestyleBase": { + "type": "object", + "properties": { + "type": { + "title": "Fdc3 UserInterface Restyle Message Type", + "const": "Fdc3UserInterfaceRestyle" + }, + "payload": { + "title": "Fdc3 UserInterface Restyle Payload", + "type": "object", + "properties": { + "updatedCSS": { + "title": "Updated CSS", + "type": "object", + "description": "A constrained set of styling properties that should be applied to the frame. Note `position` cannot be set, and should always be `fixed`.", + "properties": { + "height": {"type": "string", "title": "height", "description": "The updated height of the iframe."}, + "width": {"type": "string", "title": "width", "description": "The updated width of the iframe."}, + "zIndex": {"type": "string", "title": "zIndex", "description": "The updated zIndex to apply to the iframe."}, + "left": {"type": "string", "title": "left", "description": "The initial left property to apply to the iframe."}, + "top": {"type": "string", "title": "top", "description": "The initial top property to apply to the iframe."}, + "bottom": {"type": "string", "title": "bottom", "description": "The initial bottom property to apply to the iframe."}, + "right": {"type": "string", "title": "right", "description": "The initial right property to apply to the iframe."}, + "transition": {"type": "string", "title": "transition", "description": "The updated transition property to apply to the iframe."}, + "maxHeight": {"type": "string", "title": "maxHeight", "description": "The updated maximum height to apply to the iframe."}, + "maxWidth": {"type": "string", "title": "maxWidth", "description": "The updated maximum with to apply to the iframe."} + }, + "required": [] + } + }, + "additionalProperties": false, + "required": ["updatedCSS"] + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + } + } +} + diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesRequest.schema.json new file mode 100644 index 00000000..3876dee3 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesRequest.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findInstancesRequest.schema.json", + "type": "object", + "title": "FindInstances Request", + "description": "A request for details of instances of a particular app.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindInstancesRequestType" + }, + "payload": { + "$ref": "#/$defs/FindInstancesRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindInstancesRequestType": { + "title": "FindInstances Request Message Type", + "const": "findInstancesRequest" + }, + "FindInstancesRequestPayload": { + "type": "object", + "title": "FindInstances Request Payload", + "properties": { + "app": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + } + }, + "required": ["app"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesResponse.schema.json new file mode 100644 index 00000000..ec387c52 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesResponse.schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findInstancesResponse.schema.json", + "type": "object", + "title": "FindInstances Response", + "description": "A response to a findInstances request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindInstancesResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/FindInstancesSuccessResponsePayload" + }, + { + "$ref": "#/$defs/FindInstancesErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindInstancesResponseType": { + "title": "FindInstances Response Message Type", + "const": "findInstancesResponse" + }, + "FindInstancesSuccessResponsePayload": { + "title": "FindInstances Response Message Payload", + "type": "object", + "description": "The message payload contains a flag indicating whether the API call was successful, plus any return values for the FDC3 API function called, or indicating that the request resulted in an error and including a standardized error message.", + "properties": { + "appIdentifiers": { + "type": "array", + "items": { + "$ref": "api.schema.json#/definitions/AppMetadata" + } + } + }, + "required": [ + "appIdentifiers" + ], + "additionalProperties": false + }, + "FindInstancesErrorResponsePayload": { + "title": "FindInstances Error Response Message Payload", + "type": "object", + "properties": { + "error": { + "title": "findInstances Errors", + "type": "string", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentRequest.schema.json new file mode 100644 index 00000000..3bdfc655 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentRequest.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findIntentRequest.schema.json", + "type": "object", + "title": "FindIntent Request", + "description": "A request for details of apps available to resolve a particular intent and context pair.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindIntentRequestType" + }, + "payload": { + "$ref": "#/$defs/FindIntentRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindIntentRequestType": { + "title": "FindIntent Request Message Type", + "const": "findIntentRequest" + }, + "FindIntentRequestPayload": { + "title": "FindIntent Request Payload", + "type": "object", + "properties": { + "intent": { + "title": "Intent name", + "type": "string" + }, + "context": { + "title": "Context argument", + "$ref": "../context/context.schema.json" + }, + "resultType": { + "title": "Result type argument", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "intent" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentResponse.schema.json new file mode 100644 index 00000000..0e8d7416 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentResponse.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findIntentResponse.schema.json", + "type": "object", + "title": "FindIntent Response", + "description": "A response to a findIntent request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindIntentResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/FindIntentSuccessResponsePayload" + }, + { + "$ref": "#/$defs/FindIntentErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindIntentResponseType": { + "title": "FindIntent Response Message Type", + "const": "findIntentResponse" + }, + "FindIntentSuccessResponsePayload": { + "title": "FindIntent Response Payload", + "type": "object", + "properties": { + "appIntent": { + "$ref": "api.schema.json#/definitions/AppIntent" + } + }, + "required": [ + "appIntent" + ], + "additionalProperties": false + }, + "FindIntentErrorResponsePayload": { + "title": "FindIntent Response Error Payload", + "type": "object", + "properties": { + "error": { + "title": "findIntent Errors", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextRequest.schema.json new file mode 100644 index 00000000..0b18240d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextRequest.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findIntentsByContextRequest.schema.json", + "type": "object", + "title": "FindIntentsByContext Request", + "description": "A request for details of intents and apps available to resolve them for a particular context.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindIntentsByContextRequestType" + }, + "payload": { + "$ref": "#/$defs/FindIntentsByContextRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindIntentsByContextRequestType": { + "title": "FindIntentsByContext Request Message Type", + "const": "findIntentsByContextRequest" + }, + "FindIntentsByContextRequestPayload": { + "title": "FindIntentsByContext Request Payload", + "type": "object", + "properties": { + "context": { + "$ref": "../context/context.schema.json" + }, + "resultType": { + "title": "Result type argument", + "type": "string" + } + }, + "required": [ + "context" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextResponse.schema.json new file mode 100644 index 00000000..c74c6bda --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextResponse.schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/findIntentsByContextResponse.schema.json", + "type": "object", + "title": "FindIntentsByContext Response", + "description": "A response to a findIntentsByContext request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/FindIntentsByContextResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/FindIntentsByContextSuccessResponsePayload" + }, + { + "$ref": "#/$defs/FindIntentsByContextErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "FindIntentsByContextResponseType": { + "title": "FindIntentsByContext Response Message Type", + "const": "findIntentsByContextResponse" + }, + "FindIntentsByContextSuccessResponsePayload": { + "title": "FindIntentsByContext Response Payload", + "type": "object", + "properties": { + "appIntents": { + "type": "array", + "items": { + "$ref": "api.schema.json#/definitions/AppIntent" + } + } + }, + "required": [ + "appIntents" + ], + "additionalProperties": false + }, + "FindIntentsByContextErrorResponsePayload": { + "title": "FindIntentsByContext Error Response Payload", + "type": "object", + "properties": { + "error": { + "title": "FindIntentsByContext Error Message", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataRequest.schema.json new file mode 100644 index 00000000..eb51811c --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getAppMetadataRequest.schema.json", + "type": "object", + "title": "GetAppMetadata Request", + "description": "A request for metadata about an app.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetAppMetadataRequestType" + }, + "payload": { + "$ref": "#/$defs/GetAppMetadataRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetAppMetadataRequestType": { + "title": "GetAppMetadata Request Message Type", + "const": "getAppMetadataRequest" + }, + "GetAppMetadataRequestPayload": { + "title": "GetAppMetadata Request Payload", + "type": "object", + "properties": { + "app": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + } + }, + "required": [ + "app" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataResponse.schema.json new file mode 100644 index 00000000..ac9d6c16 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataResponse.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getAppMetadataResponse.schema.json", + "type": "object", + "title": "GetAppMetadata Response", + "description": "A response to a getAppMetadata request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetAppMetadataResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetAppMetadataSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetAppMetadataErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetAppMetadataResponseType": { + "title": "GetAppMetadata Response Message Type", + "const": "getAppMetadataResponse" + }, + "GetAppMetadataSuccessResponsePayload": { + "title": "GetAppMetadata Response Payload", + "type": "object", + "properties": { + "appMetadata": { + "$ref": "api.schema.json#/definitions/AppMetadata" + } + }, + "required": [ + "appMetadata" + ], + "additionalProperties": false + }, + "GetAppMetadataErrorResponsePayload": { + "title": "GetAppMetadata Error Response Payload", + "type": "object", + "properties": { + "error": { + "title": "GetAppMetadata Error Message", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelRequest.schema.json new file mode 100644 index 00000000..bf8a803d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getCurrentChannelRequest.schema.json", + "type": "object", + "title": "GetCurrentChannel Request", + "description": "A request to return the Channel object for the current User channel membership. Returns `null` if the app is not joined to a channel.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetCurrentChannelRequestType" + }, + "payload": { + "$ref": "#/$defs/GetCurrentChannelRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetCurrentChannelRequestType": { + "title": "GetCurrentChannel Request Message Type", + "const": "getCurrentChannelRequest" + }, + "GetCurrentChannelRequestPayload": { + "title": "GetCurrentChannel Request Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelResponse.schema.json new file mode 100644 index 00000000..312f3e17 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelResponse.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getCurrentChannelResponse.schema.json", + "type": "object", + "title": "GetCurrentChannel Response", + "description": "A response to a getCurrentChannel request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetCurrentChannelResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetCurrentChannelSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetCurrentChannelErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetCurrentChannelResponseType": { + "title": "GetCurrentChannel Response Message Type", + "const": "getCurrentChannelResponse" + }, + "GetCurrentChannelSuccessResponsePayload": { + "title": "GetCurrentChannel Response Payload", + "type": "object", + "properties": { + "channel": { + "oneOf": [ + { "$ref": "api.schema.json#/definitions/Channel" }, + { "type": "null" } + ] + } + }, + "required": [ + "channel" + ], + "additionalProperties": false + }, + "GetCurrentChannelErrorResponsePayload": { + "title": "GetCurrentChannel Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "common.schema.json#/$defs/ErrorMessages" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextRequest.schema.json new file mode 100644 index 00000000..9c7fad39 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextRequest.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getCurrentContextRequest.schema.json", + "type": "object", + "title": "GetCurrentContext Request", + "description": "A request to return the current context (either of a specified type or most recent broadcast) of a specified Channel. Returns `null` if no context (of the requested type if one was specified) is available in the channel.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetCurrentContextRequestType" + }, + "payload": { + "$ref": "#/$defs/GetCurrentContextRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetCurrentContextRequestType": { + "title": "GetCurrentContext Request Message Type", + "const": "getCurrentContextRequest" + }, + "GetCurrentContextRequestPayload": { + "title": "GetCurrentContext Request Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The id of the channel to return the current context of.", + "type": "string" + }, + "contextType": { + "title": "Context type", + "description": "The type of context to return for OR `null` indicating that the most recently broadcast context on the channel should be returned.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["channelId", "contextType"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextResponse.schema.json new file mode 100644 index 00000000..d1762052 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextResponse.schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getCurrentContextResponse.schema.json", + "type": "object", + "title": "GetCurrentContext Response", + "description": "A response to a getCurrentContext request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetCurrentContextResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetCurrentContextSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetCurrentContextErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetCurrentContextResponseType": { + "title": "GetCurrentContext Response Message Type", + "const": "getCurrentContextResponse" + }, + "GetCurrentContextSuccessResponsePayload": { + "title": "GetCurrentContext Response Payload", + "type": "object", + "properties": { + "context": { + "title": "Current Context", + "description": "The most recently broadcast context object (of the specified type, if one was specified), or `null` if none was available in the channel.", + "oneOf": [ + { "$ref": "../context/context.schema.json" }, + { "type": "null" } + ] + }, + "metadata": { + "title": "Context Metadata", + "description": "Metadata relating to the most recently broadcast context object, if available. This is not returned by the public getCurrentContext API but is used internally by the Desktop Agent proxy to deliver metadata to context listeners when replaying context after a channel change.", + "oneOf": [ + { "$ref": "api.schema.json#/definitions/ContextMetadata" }, + { "type": "null" } + ] + } + }, + "required": [ + "context" + ], + "additionalProperties": false + }, + "GetCurrentContextErrorResponsePayload": { + "title": "GetCurrentContext Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoRequest.schema.json new file mode 100644 index 00000000..c8158cf6 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getInfoRequest.schema.json", + "type": "object", + "title": "GetInfo Request", + "description": "Request to retrieve information about the FDC3 Desktop Agent implementation and the metadata of the calling application according to the Desktop Agent.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetInfoRequestType" + }, + "payload": { + "$ref": "#/$defs/GetInfoRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetInfoRequestType": { + "title": "GetInfo Request Message Type", + "const": "getInfoRequest" + }, + "GetInfoRequestPayload": { + "title": "GetInfo Request Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoResponse.schema.json new file mode 100644 index 00000000..8c7ecc4f --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getInfoResponse.schema.json", + "type": "object", + "title": "GetInfo Response", + "description": "A response to a getInfo request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetInfoResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetInfoSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetInfoErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetInfoResponseType": { + "title": "GetInfo Response Message Type", + "const": "getInfoResponse" + }, + "GetInfoSuccessResponsePayload": { + "title": "GetInfo Success Response Payload", + "type": "object", + "properties": { + "implementationMetadata": { + "$ref": "api.schema.json#/definitions/ImplementationMetadata" + } + }, + "required": [ + "implementationMetadata" + ], + "additionalProperties": false + }, + "GetInfoErrorResponsePayload": { + "title": "GetInfo Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "common.schema.json#/$defs/ErrorMessages" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelRequest.schema.json new file mode 100644 index 00000000..9b6354d8 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getOrCreateChannelRequest.schema.json", + "type": "object", + "title": "GetOrCreateChannel Request", + "description": "Request to return a Channel with an auto-generated identity that is intended for private communication between applications.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetOrCreateChannelRequestType" + }, + "payload": { + "$ref": "#/$defs/GetOrCreateChannelRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetOrCreateChannelRequestType": { + "title": "GetOrCreateChannel Request Message Type", + "const": "getOrCreateChannelRequest" + }, + "GetOrCreateChannelRequestPayload": { + "title": "GetOrCreateChannel Request Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The id of the channel to return", + "type": "string" + } + }, + "additionalProperties": false, + "required": ["channelId"] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelResponse.schema.json new file mode 100644 index 00000000..d887c827 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getOrCreateChannelResponse.schema.json", + "type": "object", + "title": "GetOrCreateChannel Response", + "description": "A response to a getOrCreateChannel request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetOrCreateChannelResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetOrCreateChannelSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetOrCreateChannelErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetOrCreateChannelResponseType": { + "title": "GetOrCreateChannel Response Message Type", + "const": "getOrCreateChannelResponse" + }, + "GetOrCreateChannelSuccessResponsePayload": { + "title": "GetOrCreateChannel Response Payload", + "type": "object", + "properties": { + "channel": { + "$ref": "api.schema.json#/definitions/Channel" + } + }, + "required": [ + "channel" + ], + "additionalProperties": false + }, + "GetOrCreateChannelErrorResponsePayload": { + "title": "GetOrCreateChannel Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsRequest.schema.json new file mode 100644 index 00000000..697c49a1 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getUserChannelsRequest.schema.json", + "type": "object", + "title": "GetUserChannels Request", + "description": "Request to retrieve a list of the User Channels available for the app to join.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetUserChannelsRequestType" + }, + "payload": { + "$ref": "#/$defs/GetUserChannelsRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetUserChannelsRequestType": { + "title": "GetUserChannels Request Message Type", + "const": "getUserChannelsRequest" + }, + "GetUserChannelsRequestPayload": { + "title": "GetUserChannels Request Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsResponse.schema.json new file mode 100644 index 00000000..ad352f21 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsResponse.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/getUserChannelsResponse.schema.json", + "type": "object", + "title": "GetUserChannels Response", + "description": "A response to a getUserChannels request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/GetUserChannelsResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/GetUserChannelsSuccessResponsePayload" + }, + { + "$ref": "#/$defs/GetUserChannelsErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "GetUserChannelsResponseType": { + "title": "GetUserChannels Response Message Type", + "const": "getUserChannelsResponse" + }, + "GetUserChannelsSuccessResponsePayload": { + "title": "GetUserChannels Response Payload", + "type": "object", + "properties": { + "userChannels": { + "type": "array", + "items": { + "$ref": "api.schema.json#/definitions/Channel" + } + } + }, + "required": [ + "userChannels" + ], + "additionalProperties": false + }, + "GetUserChannelsErrorResponsePayload": { + "title": "GetUserChannels Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatAcknowledgmentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatAcknowledgmentRequest.schema.json new file mode 100644 index 00000000..b500ab1b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatAcknowledgmentRequest.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/heartbeatAcknowledgementRequest.schema.json", + "type": "object", + "title": "HeartbeatAcknowledgement Request", + "description": "A request that serves as an acknowledgement of a heartbeat event from the Desktop Agent and indicates that an application window or frame is still alive.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/HeartbeatAcknowledgementRequestType" + }, + "payload": { + "$ref": "#/$defs/HeartbeatAcknowledgementRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "HeartbeatAcknowledgementRequestType": { + "title": "HeartbeatAcknowledgement Request Message Type", + "const": "heartbeatAcknowledgementRequest" + }, + "HeartbeatAcknowledgementRequestPayload": { + "title": "heartbeatAcknowledgement Request Payload", + "type": "object", + "properties": { + "heartbeatEventUuid": { + "title": "Heartbeat Event Uuid", + "type": "string", + "description": "The eventUuid value of the HeartbeatEvent that the acknowledgement being sent relates to." + } + }, + "additionalProperties": false, + "required": [ + "heartbeatEventUuid" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatEvent.schema.json new file mode 100644 index 00000000..e1bdd970 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatEvent.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/heartbeatEvent.schema.json", + "type": "object", + "title": "Heartbeat Event", + "description": "A heartbeat message from the Desktop Agent to an app indicating that the Desktop Agent is alive and that the application should send a heartbeatResponseRequest to the agent in response.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/HeartbeatEventType" + }, + "payload": { + "$ref": "#/$defs/HeartbeatEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "HeartbeatEventType": { + "title": "Heartbeat Event Message Type", + "const": "heartbeatEvent" + }, + "HeartbeatEventPayload": { + "title": "heartbeat Event Payload", + "type": "object", + "properties": {}, + "additionalProperties": false, + "required": [] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentEvent.schema.json new file mode 100644 index 00000000..05ffaaff --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentEvent.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/intentEvent.schema.json", + "type": "object", + "title": "intent Event", + "description": "An event message from the Desktop Agent to an app indicating that it has been selected to resolve a raised intent and context.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/IntentEventType" + }, + "payload": { + "$ref": "#/$defs/IntentEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "IntentEventType": { + "title": "Intent Event Message Type", + "const": "intentEvent" + }, + "IntentEventPayload": { + "title": "Intent Event Payload", + "type": "object", + "properties": { + "intent": { + "title": "Intent", + "description": "The intent that was raised.", + "type": "string" + }, + "context": { + "$ref": "../context/context.schema.json", + "title": "Context", + "description": "The context object passed with the raised intent." + }, + "metadata": { + "$ref": "api.schema.json#/definitions/ContextMetadata" + }, + "raiseIntentRequestUuid": { + "title": "raiseIntentRequest UUID", + "type": "string", + "description": "The requestUuid value of the raiseIntentRequest that the intentEvent being sent relates to." + } + }, + "additionalProperties": false, + "required": [ + "intent", "context", "raiseIntentRequestUuid", "metadata" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeRequest.schema.json new file mode 100644 index 00000000..1de21f60 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/intentListenerUnsubscribeRequest.schema.json", + "type": "object", + "title": "IntentListenerUnsubscribe Request", + "description": "A request to unsubscribe a context listener.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/IntentListenerUnsubscribeRequestType" + }, + "payload": { + "$ref": "#/$defs/IntentListenerUnsubscribeRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "IntentListenerUnsubscribeRequestType": { + "title": "IntentListenerUnsubscribe Request Message Type", + "const": "intentListenerUnsubscribeRequest" + }, + "IntentListenerUnsubscribeRequestPayload": { + "title": "IntentListenerUnsubscribe Request Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeResponse.schema.json new file mode 100644 index 00000000..582ace79 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/intentListenerUnsubscribeResponse.schema.json", + "type": "object", + "title": "IntentListenerUnsubscribe Response", + "description": "A response to a intentListenerUnsubscribe request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/IntentListenerUnsubscribeResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "IntentListenerUnsubscribeResponseType": { + "title": "IntentListenerUnsubscribe Response Message Type", + "const": "intentListenerUnsubscribeResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultRequest.schema.json new file mode 100644 index 00000000..dc75ac89 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultRequest.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/intentResultRequest.schema.json", + "type": "object", + "title": "IntentResult Request", + "description": "A request to deliver a result for an intent (which may include a `void` result that just indicates that the handler has run, returning no result). The result is tied to the intentEvent it relates to by quoting the `eventUuid` of the intentEvent in its payload.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/IntentResultRequestType" + }, + "payload": { + "$ref": "#/$defs/IntentResultRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "IntentResultRequestType": { + "title": "IntentResult Request Message Type", + "const": "intentResultRequest" + }, + "IntentResultRequestPayload": { + "title": "IntentResult Request Payload", + "type": "object", + "properties": { + "intentEventUuid": { + "title": "IntentEvent UUID", + "type": "string", + "description": "The eventUuid value of the intentEvent that the result being sent relates to." + }, + "raiseIntentRequestUuid": { + "title": "raiseIntentRequest UUID", + "type": "string", + "description": "The requestUuid value of the raiseIntentRequest that the result being sent relates to." + }, + "intentResult": { + "$ref": "api.schema.json#/definitions/IntentResult" + }, + "metadata": { + "$ref": "api.schema.json#/definitions/AppProvidableContextMetadata", + "description": "Optional app-provided metadata returned by the intent handler alongside a context result (i.e. when the handler returns a ContextWithMetadata object). The Desktop Agent will merge this with its own generated metadata before delivering to the raising app via raiseIntentResultResponse." + } + }, + "required": [ + "intentEventUuid", "raiseIntentRequestUuid", "intentResult" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultResponse.schema.json new file mode 100644 index 00000000..6475d1d6 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/intentResultResponse.schema.json", + "type": "object", + "title": "IntentResult Response", + "description": "A response to a request to deliver an intent result.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/IntentResultResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "IntentResultResponseType": { + "title": "IntentResult Response Message Type", + "const": "intentResultResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelRequest.schema.json new file mode 100644 index 00000000..84203f30 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/joinUserChannelRequest.schema.json", + "type": "object", + "title": "JoinUserChannel Request", + "description": "Request to join the app to the specified User channel. On successfully joining a channel, client code should make subsequent requests to get the current context of that channel for all registered context listeners and then call their handlers with it.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/JoinUserChannelRequestType" + }, + "payload": { + "$ref": "#/$defs/JoinUserChannelRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "JoinUserChannelRequestType": { + "title": "JoinUserChannel Request Message Type", + "const": "joinUserChannelRequest" + }, + "JoinUserChannelRequestPayload": { + "title": "JoinUserChannel Request Payload", + "type": "object", + "properties": { + "channelId": { + "title": "Channel Id", + "description": "The id of the channel to join.", + "type": "string" + } + }, + "additionalProperties": false, + "required": ["channelId"] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelResponse.schema.json new file mode 100644 index 00000000..fe6aeedc --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelResponse.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/joinUserChannelResponse.schema.json", + "type": "object", + "title": "JoinUserChannel Response", + "description": "A response to a joinUserChannel request. On receipt of this response, client code should make subsequent requests to get the current context of that channel for all registered context listeners and then call their handlers with it.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/JoinUserChannelResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/JoinUserChannelSuccessResponsePayload" + }, + { + "$ref": "#/$defs/JoinUserChannelErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "JoinUserChannelResponseType": { + "title": "JoinUserChannel Response Message Type", + "const": "joinUserChannelResponse" + }, + "JoinUserChannelSuccessResponsePayload": { + "title": "JoinUserChannel Response Payload", + "type": "object", + "properties": { + + }, + "additionalProperties": false + }, + "JoinUserChannelErrorResponsePayload": { + "title": "JoinUserChannel Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelRequest.schema.json new file mode 100644 index 00000000..d78488fc --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelRequest.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/leaveCurrentChannelRequest.schema.json", + "type": "object", + "title": "LeaveCurrentChannel Request", + "description": "Request to remove the app from any User channel membership.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/LeaveCurrentChannelRequestType" + }, + "payload": { + "$ref": "#/$defs/LeaveCurrentChannelRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "LeaveCurrentChannelRequestType": { + "title": "LeaveCurrentChannel Request Message Type", + "const": "leaveCurrentChannelRequest" + }, + "LeaveCurrentChannelRequestPayload": { + "title": "LeaveCurrentChannel Request Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelResponse.schema.json new file mode 100644 index 00000000..9dc9d093 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelResponse.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/leaveCurrentChannelResponse.schema.json", + "type": "object", + "title": "LeaveCurrentChannel Response", + "description": "A response to a leaveCurrentChannel request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/LeaveCurrentChannelResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/LeaveCurrentChannelSuccessResponsePayload" + }, + { + "$ref": "#/$defs/LeaveCurrentChannelErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "LeaveCurrentChannelResponseType": { + "title": "LeaveCurrentChannel Response Message Type", + "const": "leaveCurrentChannelResponse" + }, + "LeaveCurrentChannelSuccessResponsePayload": { + "title": "LeaveCurrentChannel Response Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "LeaveCurrentChannelErrorResponsePayload": { + "title": "LeaveCurrentChannel Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/openRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/openRequest.schema.json new file mode 100644 index 00000000..2f7f5122 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/openRequest.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/openRequest.schema.json", + "type": "object", + "title": "Open Request", + "description": "A request to open an application.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/OpenRequestType" + }, + "payload": { + "$ref": "#/$defs/OpenRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "OpenRequestType": { + "title": "Open Request Message Type", + "const": "openRequest" + }, + "OpenRequestPayload": { + "title": "Open Request Payload", + "type": "object", + "properties": { + "app": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + }, + "context": { + "$ref": "../context/context.schema.json", + "title": "Context", + "description": "If a Context object is passed in, this object will be provided to the opened application via a contextListener. The Context argument is functionally equivalent to opening the target app with no context and broadcasting the context directly to it." + }, + "metadata": { + "$ref": "api.schema.json#/definitions/AppProvidableContextMetadata" + } + }, + "required": [ + "app", "metadata" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/openResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/openResponse.schema.json new file mode 100644 index 00000000..c5ea42bb --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/openResponse.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/openResponse.schema.json", + "type": "object", + "title": "Open Response", + "description": "A response to a open request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/OpenResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/OpenSuccessResponsePayload" + }, + { + "$ref": "#/$defs/OpenErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "OpenResponseType": { + "title": "Open Response Message Type", + "const": "openResponse" + }, + "OpenSuccessResponsePayload": { + "title": "Open Response Payload", + "type": "object", + "properties": { + "appIdentifier": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + } + }, + "required": [ + "appIdentifier" + ], + "additionalProperties": false + }, + "OpenErrorResponsePayload": { + "title": "Open Error Response Payload", + "type": "object", + "properties": { + "error": { + "title": "Open Error Response Payload", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/OpenError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerRequest.schema.json new file mode 100644 index 00000000..7cc8a3b3 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerRequest.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelAddEventListenerRequest.schema.json", + "type": "object", + "title": "PrivateChannelAddEventListener Request", + "description": "A request to add an event listener to a specific PrivateChannel.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelAddEventListenerRequestType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelAddEventListenerRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelAddEventListenerRequestType": { + "title": "PrivateChannelAddEventListener Request Message Type", + "const": "privateChannelAddEventListenerRequest" + }, + "PrivateChannelAddEventListenerRequestPayload": { + "title": "PrivateChannelAddEventListener Request Payload", + "type": "object", + "properties": { + "privateChannelId": { + "title": "Private Channel Id", + "description": "The Id of the PrivateChannel that the listener should be added to.", + "type": "string" + }, + "listenerType": { + "title": "Event listener type", + "description": "The type of PrivateChannel event that the listener should be applied to, or null for all event types.", + "oneOf" : [ + { "$ref": "api.schema.json#/definitions/PrivateChannelEventType" }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "privateChannelId", + "listenerType" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerResponse.schema.json new file mode 100644 index 00000000..a02fc50b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerResponse.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelAddEventListenerResponse.schema.json", + "type": "object", + "title": "PrivateChannelAddEventListener Response", + "description": "A response to a privateChannelAddEventListener request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelAddEventListenerResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/PrivateChannelAddEventListenerSuccessResponsePayload" + }, + { + "$ref": "#/$defs/PrivateChannelAddEventListenerErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelAddEventListenerResponseType": { + "title": "PrivateChannelAddEventListener Response Message Type", + "const": "privateChannelAddEventListenerResponse" + }, + "PrivateChannelAddEventListenerSuccessResponsePayload": { + "title": "PrivateChannelAddEventListener Response Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + }, + "PrivateChannelAddEventListenerErrorResponsePayload": { + "title": "PrivateChannelAddEventListener Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectRequest.schema.json new file mode 100644 index 00000000..0c098676 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelDisconnectRequest.schema.json", + "type": "object", + "title": "PrivateChannelDisconnect Request", + "description": "Request that indicates that a participant will no longer interact with a specified `PrivateChannel`.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelDisconnectRequestType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelDisconnectRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelDisconnectRequestType": { + "title": "PrivateChannelDisconnect Request Message Type", + "const": "privateChannelDisconnectRequest" + }, + "PrivateChannelDisconnectRequestPayload": { + "title": "PrivateChannelDisconnect Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The Id of the Channel that should be disconnected from" + } + }, + "required": ["channelId"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectResponse.schema.json new file mode 100644 index 00000000..56eabb6e --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectResponse.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelDisconnectResponse.schema.json", + "type": "object", + "title": "PrivateChannelDisconnect Response", + "description": "A response to a privateChannelDisconnect request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelDisconnectResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/PrivateChannelDisconnectSuccessResponsePayload" + }, + { + "$ref": "#/$defs/PrivateChannelDisconnectErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelDisconnectResponseType": { + "title": "PrivateChannelDisconnect Response Message Type", + "const": "privateChannelDisconnectResponse" + }, + "PrivateChannelDisconnectSuccessResponsePayload": { + "title": "PrivateChannelDisconnect Response Payload", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "PrivateChannelDisconnectErrorResponsePayload": { + "title": "PrivateChannelDisconnect Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "api.schema.json#/definitions/ChannelError" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnAddContextListenerEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnAddContextListenerEvent.schema.json new file mode 100644 index 00000000..3b713bc9 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnAddContextListenerEvent.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelOnAddContextListenerEvent.schema.json", + "type": "object", + "title": "privateChannelOnAddContextListener Event", + "description": "An event message from the Desktop Agent to an app indicating that another app has added a context listener to a specific PrivateChannel.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelOnAddContextListenerEventType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelOnAddContextListenerEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelOnAddContextListenerEventType": { + "title": "PrivateChannelOnAddContextListener Event Message Type", + "const": "privateChannelOnAddContextListenerEvent" + }, + "PrivateChannelOnAddContextListenerEventPayload": { + "title": "privateChannelOnAddContextListener Event Payload", + "type": "object", + "properties": { + "privateChannelId": { + "type": "string", + "title": "Private Channel Id", + "description": "The Id of the PrivateChannel that the listener was added to." + }, + "contextType": { + "title": "Context type", + "description": "The type of the context listener added to the channel by another app, or null if it will listen to all types.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "privateChannelId", + "contextType" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnDisconnectEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnDisconnectEvent.schema.json new file mode 100644 index 00000000..9a0432ad --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnDisconnectEvent.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelOnDisconnectEvent.schema.json", + "type": "object", + "title": "privateChannelOnDisconnect Event", + "description": "An event message from the Desktop Agent to an app indicating that another app has disconnected from a specific PrivateChannel and will no longer interact with it.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelOnDisconnectEventType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelOnDisconnectEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelOnDisconnectEventType": { + "title": "PrivateChannelOnDisconnect Event Message Type", + "const": "privateChannelOnDisconnectEvent" + }, + "PrivateChannelOnDisconnectEventPayload": { + "title": "privateChannelOnDisconnect Event Payload", + "type": "object", + "properties": { + "privateChannelId": { + "type": "string", + "title": "Private Channel Id", + "description": "The Id of the PrivateChannel that the app has disconnected from." + } + }, + "additionalProperties": false, + "required": [ + "privateChannelId" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnUnsubscribeEvent.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnUnsubscribeEvent.schema.json new file mode 100644 index 00000000..2ae1781b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnUnsubscribeEvent.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelOnUnsubscribeEvent.schema.json", + "type": "object", + "title": "PrivateChannelOnUnsubscribe Event", + "description": "An event message from the Desktop Agent to an app indicating that another app has unsubscribed a context listener from a specific PrivateChannel.", + "allOf": [ + { + "$ref": "agentEvent.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelOnUnsubscribeEventType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelOnUnsubscribeEventPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelOnUnsubscribeEventType": { + "title": "PrivateChannelOnUnsubscribe Event Message Type", + "const": "privateChannelOnUnsubscribeEvent" + }, + "PrivateChannelOnUnsubscribeEventPayload": { + "title": "privateChannelOnUnsubscribe Event Payload", + "type": "object", + "properties": { + "privateChannelId": { + "type": "string", + "title": "Private Channel Id", + "description": "The Id of the PrivateChannel that the listener was unsubscribed from." + }, + "contextType": { + "title": "Context type", + "description": "The type of the context listener unsubscribed from the channel by another app, or null if it was listening to all types.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "privateChannelId", + "contextType" + ] + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerRequest.schema.json new file mode 100644 index 00000000..658ebcce --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerRequest.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelUnsubscribeEventListenerRequest.schema.json", + "type": "object", + "title": "PrivateChannelUnsubscribeEventListener Request", + "description": "A request to unsubscribe a context listener.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelUnsubscribeEventListenerRequestType" + }, + "payload": { + "$ref": "#/$defs/PrivateChannelUnsubscribeEventListenerRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelUnsubscribeEventListenerRequestType": { + "title": "PrivateChannelUnsubscribeEventListener Request Message Type", + "const": "privateChannelUnsubscribeEventListenerRequest" + }, + "PrivateChannelUnsubscribeEventListenerRequestPayload": { + "title": "PrivateChannelUnsubscribeEventListener Request Payload", + "type": "object", + "properties": { + "listenerUUID": { + "$ref": "common.schema.json#/$defs/ListenerUuid" + } + }, + "required": [ + "listenerUUID" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerResponse.schema.json new file mode 100644 index 00000000..327a72c2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerResponse.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/privateChannelUnsubscribeEventListenerResponse.schema.json", + "type": "object", + "title": "PrivateChannelUnsubscribeEventListener Response", + "description": "A response to a privateChannelUnsubscribeEventListener request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/PrivateChannelUnsubscribeEventListenerResponseType" + }, + "payload": true, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "PrivateChannelUnsubscribeEventListenerResponseType": { + "title": "PrivateChannelUnsubscribeEventListener Response Message Type", + "const": "privateChannelUnsubscribeEventListenerResponse" + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextRequest.schema.json new file mode 100644 index 00000000..e22ad86d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextRequest.schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/raiseIntentForContextRequest.schema.json", + "type": "object", + "title": "RaiseIntentForContext Request", + "description": "A request to raise an unspecified intent for a specified context.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/RaiseIntentForContextRequestType" + }, + "payload": { + "$ref": "#/$defs/RaiseIntentForContextRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "RaiseIntentForContextRequestType": { + "title": "RaiseIntentForContext Request Message Type", + "const": "raiseIntentForContextRequest" + }, + "RaiseIntentForContextRequestPayload": { + "title": "RaiseIntentForContext Request Payload", + "type": "object", + "properties": { + "context": { + "$ref": "../context/context.schema.json" + }, + "app": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + }, + "metadata": { + "$ref": "api.schema.json#/definitions/AppProvidableContextMetadata" + } + }, + "required": [ + "context", "metadata" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextResponse.schema.json new file mode 100644 index 00000000..6cd74099 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextResponse.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/raiseIntentForContextResponse.schema.json", + "type": "object", + "title": "RaiseIntentForContext Response", + "description": "A response to a raiseIntentForContext request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/RaiseIntentForContextResponseType" + }, + "payload": { + "title": "RaiseIntentForContext Response Payload", + "description": "There are 3 possible responses to a raiseIntentForContext request, each of which sets a single property in the payload: Success (`intentResolution`), Needs further resolution (`appIntents`) or Error (`error`).", + "oneOf": [ + { + "$ref": "raiseIntentResponse.schema.json#/$defs/RaiseIntentSuccessResponsePayload" + }, + { + "$ref": "#/$defs/RaiseIntentForContextNeedsResolutionResponsePayload" + }, + { + "$ref": "raiseIntentResponse.schema.json#/$defs/RaiseIntentErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "RaiseIntentForContextResponseType": { + "title": "RaiseIntentForContext Response Message Type", + "const": "raiseIntentForContextResponse" + }, + "RaiseIntentForContextNeedsResolutionResponsePayload": { + "title": "RaiseIntentForContext NeedsResolution Response Payload", + "description": "Response to a raiseIntentForContext request that needs additional resolution (i.e. show an intent resolver UI).", + "type": "object", + "properties": { + "appIntents": { + "title": "AppIntents", + "description": "Used if a raiseIntentForContext request requires additional resolution (e.g. by showing an intent resolver) before it can be handled.", + "type": "array", + "items": { + "$ref": "api.schema.json#/definitions/AppIntent" + } + } + }, + "required": [ + "appIntents" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentRequest.schema.json new file mode 100644 index 00000000..aa94c444 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentRequest.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/raiseIntentRequest.schema.json", + "type": "object", + "title": "RaiseIntent Request", + "description": "A request to raise an intent for a context.", + "allOf": [ + { + "$ref": "appRequest.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/RaiseIntentRequestType" + }, + "payload": { + "$ref": "#/$defs/RaiseIntentRequestPayload" + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "RaiseIntentRequestType": { + "title": "RaiseIntent Request Message Type", + "const": "raiseIntentRequest" + }, + "RaiseIntentRequestPayload": { + "title": "RaiseIntent Request Payload", + "type": "object", + "properties": { + "intent": { + "type": "string" + }, + "context": { + "$ref": "../context/context.schema.json" + }, + "app": { + "$ref": "api.schema.json#/definitions/AppIdentifier" + }, + "metadata": { + "$ref": "api.schema.json#/definitions/AppProvidableContextMetadata" + } + }, + "required": [ + "intent", "context", "metadata" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResponse.schema.json new file mode 100644 index 00000000..5f85fd8e --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResponse.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/raiseIntentResponse.schema.json", + "type": "object", + "title": "RaiseIntent Response", + "description": "A response to a raiseIntent request.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/RaiseIntentResponseType" + }, + "payload": { + "title": "RaiseIntent Response Payload", + "description": "There are 3 possible responses to a raiseIntent request, each of which sets a single property in the payload: Success (`intentResolution`), Needs further resolution (`appIntent`) or Error (`error`).", + "oneOf": [ + { + "$ref": "#/$defs/RaiseIntentSuccessResponsePayload" + }, + { + "$ref": "#/$defs/RaiseIntentNeedsResolutionResponsePayload" + }, + { + "$ref": "#/$defs/RaiseIntentErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "RaiseIntentResponseType": { + "title": "RaiseIntent Response Message Type", + "const": "raiseIntentResponse" + }, + "RaiseIntentSuccessResponsePayload": { + "title": "RaiseIntent Success Response Payload", + "type": "object", + "properties": { + "intentResolution": { + "title": "Intent Resolution", + "description": "Used if the raiseIntent request was successfully resolved.", + "$ref": "api.schema.json#/definitions/IntentResolution" + } + }, + "required": [ + "intentResolution" + ], + "additionalProperties": false + }, + "RaiseIntentNeedsResolutionResponsePayload": { + "title": "RaiseIntent NeedsResolution Response Payload", + "description": "Response to a raiseIntent request that needs additional resolution (i.e. show an intent resolver UI).", + "type": "object", + "properties": { + "appIntent": { + "title": "AppIntent", + "description": "Used if a raiseIntent request requires additional resolution (e.g. by showing an intent resolver) before it can be handled.", + "$ref": "api.schema.json#/definitions/AppIntent" + } + }, + "required": [ + "appIntent" + ], + "additionalProperties": false + }, + "RaiseIntentErrorResponsePayload": { + "title": "RaiseIntent Error Response Payload", + "description": "Used if a raiseIntent request resulted in an error.", + "type": "object", + "properties": { + "error": { + "title": "RaiseIntent Error", + "description": "Should be set if the raiseIntent request returned an error.", + "oneOf": [ + { + "$ref": "api.schema.json#/definitions/ResolveError" + }, + { + "$ref": "api.schema.json#/definitions/BridgingError" + } + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResultResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResultResponse.schema.json new file mode 100644 index 00000000..7ea77143 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResultResponse.schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/api/raiseIntentResultResponse.schema.json", + "type": "object", + "title": "RaiseIntentResult Response", + "description": "A secondary response to a request to raise an intent used to deliver the intent result. This message should quote the original requestUuid of the raiseIntentRequest message in its `meta.requestUuid` field.", + "allOf": [ + { + "$ref": "agentResponse.schema.json" + }, + { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/RaiseIntentResultResponseType" + }, + "payload": { + "oneOf": [ + { + "$ref": "#/$defs/RaiseIntentResultSuccessResponsePayload" + }, + { + "$ref": "#/$defs/RaiseIntentResultErrorResponsePayload" + } + ] + }, + "meta": true + }, + "additionalProperties": false + } + ], + "$defs": { + "RaiseIntentResultResponseType": { + "title": "RaiseIntentResult Response Message Type", + "const": "raiseIntentResultResponse" + }, + "RaiseIntentResultSuccessResponsePayload": { + "title": "RaiseIntent Result Response Payload", + "type": "object", + "properties": { + "intentResult": { + "$ref": "api.schema.json#/definitions/IntentResult" + }, + "resultMetadata": { + "$ref": "api.schema.json#/definitions/ContextMetadata", + "description": "Metadata for the intent result, generated by the Desktop Agent and merged with any app-provided metadata from the intentResultRequest. Always present, even for void or channel results." + } + }, + "required": [ + "intentResult" + ], + "additionalProperties": false + }, + "RaiseIntentResultErrorResponsePayload": { + "title": "RaiseIntentResult Error Response Payload", + "type": "object", + "properties": { + "error": { + "$ref": "common.schema.json#/$defs/ErrorMessages" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/api/t2sQuicktypeUtil.js b/fdc3-schema/src/main/schemas-temp/3.0.0/api/t2sQuicktypeUtil.js new file mode 100644 index 00000000..a00d531a --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/t2sQuicktypeUtil.js @@ -0,0 +1,69 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright FINOS FDC3 contributors - see NOTICE file + */ + +/** Utility for preparing arguments to quicktype, which workaround a specific + * quicktype bug in command line argument handling (where a directory is used + * as input the source language argument is ignored which causes our schemas + * to be interpreted as JSON input, rather than JSONSchema). + * Bug issue: + * */ + +const path = require('path'); +const fs = require('fs'); +const exec = require('child_process').exec; + +const args = process.argv.slice(2); +const outputPath = args.pop(); +const inputs = args; + +console.log('Inputs: ' + inputs.join(' | ')); +console.log('Output path argument: ' + outputPath); + +let source = ''; + +let dirIndex = 0; + +const excludedTypes = [ + 'DesktopAgent.ts', + 'Listener.ts', + 'Methods.ts', + 'PrivateChannel.ts', + 'Types.ts', + 'RecommendedChannels.ts', +]; + +let sources = ''; + +while (dirIndex < inputs.length) { + if (inputs[dirIndex].endsWith('.ts')) { + sources += `--src ${path.join(inputs[dirIndex])} `; + } else { + fs.readdirSync(inputs[dirIndex], { withFileTypes: true }).forEach(file => { + if (file.isDirectory()) { + inputs.push(path.join(inputs[dirIndex], file.name)); + } else { + if (!excludedTypes.includes(file.name)) { + sources += `--src ${path.join(inputs[dirIndex], file.name)} `; + } + } + }); + } + dirIndex++; +} + +// Normalise path to local quicktype executable. +const quicktypeExec = ['.', 'node_modules', '.bin', 'quicktype'].join(path.sep); + +const command = `${quicktypeExec} -l schema -o ${outputPath} ${sources}`; +console.log('command to run: ' + command); + +exec(command, function (error, stdout, stderr) { + if (stdout) { + console.log(stdout); + } + if (stderr) { + console.log(stderr); + } +}); diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentErrorResponse.schema.json new file mode 100644 index 00000000..67b22bc1 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentErrorResponse.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/agentErrorResponse.schema.json", + "title": "Agent Error Response Message", + "type": "object", + "description": "A response message from a Desktop Agent to the Bridge containing an error, to be used in preference to the standard response when an error needs to be returned.", + "properties": { + "type": { + "title": "Response Message Type", + "type": "string", + "enum": [ + "findInstancesResponse", + "findIntentResponse", + "findIntentsByContextResponse", + "getAppMetadataResponse", + "openResponse", + "raiseIntentResponse", + "raiseIntentResultResponse" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Error Response Message Payload", + "type": "object", + "description": "Error message payload containing an standardized error string.", + "properties": { + "error": { + "$ref": "../api/common.schema.json#/$defs/ErrorMessages" + } + }, + "unevaluatedProperties": false, + "required": ["error"] + }, + "meta": { + "$ref": "agentResponse.schema.json#/$defs/AgentResponseMeta" + } + }, + "additionalProperties": false, + "required": ["type", "payload", "meta"] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentRequest.schema.json new file mode 100644 index 00000000..edf763ef --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentRequest.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/agentRequest.schema.json", + "title": "Agent Request Message", + "type": "object", + "description": "A request message from a Desktop Agent to the Bridge.", + "properties": { + "type": { + "title": "Request Message type", + "type": "string", + "enum": [ + "broadcastRequest", + "findInstancesRequest", + "findIntentRequest", + "findIntentsByContextRequest", + "getAppMetadataRequest", + "openRequest", + "PrivateChannel.broadcast", + "PrivateChannel.eventListenerAdded", + "PrivateChannel.eventListenerRemoved", + "PrivateChannel.onAddContextListener", + "PrivateChannel.onDisconnect", + "PrivateChannel.onUnsubscribe", + "raiseIntentRequest" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Request' appended." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload typically contains the arguments to FDC3 API functions." + }, + "meta": { + "$ref": "#/$defs/AgentRequestMeta" + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false, + "$defs": { + "AgentRequestMeta": { + "title": "Agent Request Metadata", + "description": "Metadata for a request message sent by Desktop Agents to the Bridge.", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + }, + "source": { + "title": "Source identifier", + "description": "Field that represents the source application that the request was received from, or the source Desktop Agent if it issued the request itself.", + "$ref": "common.schema.json#/$defs/RequestSource" + }, + "destination": { + "title": "Destination identifier", + "description": "Optional field that represents the destination that the request should be routed to. Must be set by the Desktop Agent for API calls that include a target app parameter and must include the name of the Desktop Agent hosting the target application.", + "$ref": "common.schema.json#/$defs/BridgeParticipantIdentifier" + } + }, + "required": ["requestUuid", "timestamp"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentResponse.schema.json new file mode 100644 index 00000000..e2d1d3a7 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentResponse.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/agentResponse.schema.json", + "title": "Agent Response Message", + "type": "object", + "description": "A response message from a Desktop Agent to the Bridge.", + "properties": { + "type": { + "title": "Response Message Type", + "type": "string", + "enum": [ + "findInstancesResponse", + "findIntentResponse", + "findIntentsByContextResponse", + "getAppMetadataResponse", + "openResponse", + "raiseIntentResponse", + "raiseIntentResultResponse" + ], + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Response Message Payload", + "type": "object", + "description": "The message payload typically contains return values for FDC3 API functions." + }, + "meta": { + "$ref": "#/$defs/AgentResponseMeta" + } + }, + "additionalProperties": false, + "required": ["type", "payload", "meta"], + "$defs": { + "AgentResponseMeta": { + "title": "Agent Response Metadata", + "description": "Metadata for a response messages sent by a Desktop Agent to the Bridge", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + } + }, + "required": ["requestUuid", "responseUuid", "timestamp"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeErrorResponse.schema.json new file mode 100644 index 00000000..31a55eb5 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeErrorResponse.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/bridgeErrorResponse.schema.json", + "title": "Bridge Error Response Message", + "type": "object", + "description": "A response message from the Bridge back to the original Desktop Agent that raised the request, used where all connected agents returned errors.", + "properties": { + "type": { + "title": "Response Message Type", + "type": "string", + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Response Error Message Payload", + "type": "object", + "description": "The error message payload contains details of an error return to the app or agent that raised the original request.", + "properties": { + "error": { + "$ref": "../api/common.schema.json#/$defs/ErrorMessages" + } + } + }, + "meta": { + "$ref": "#/$defs/BridgeErrorResponseMeta" + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false, + "$defs": { + "BridgeErrorResponseMeta": { + "title": "Bridge Response Metadata", + "description": "Metadata required in a response message collated and/or forwarded on by the Bridge", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + }, + "errorSources": { + "$ref": "common.schema.json#/$defs/BridgeResponseErrorSources" + }, + "errorDetails": { + "$ref": "common.schema.json#/$defs/BridgeResponseErrorDetails" + } + }, + "required": ["requestUuid", "responseUuid", "timestamp", "errorSources", "errorDetails"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeRequest.schema.json new file mode 100644 index 00000000..cb4bf0e6 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeRequest.schema.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/bridgeRequest.schema.json", + "title": "Bridge Request Message", + "type": "object", + "description": "A request message forwarded from the Bridge onto a Desktop Agent connected to it.", + "properties": { + "type": { + "title": "Message type", + "type": "string", + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Request' appended." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload typically contains the arguments to FDC3 API functions." + }, + "meta": { + "$ref": "#/$defs/BridgeRequestMeta" + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false, + "$defs": { + "BridgeRequestMeta": { + "title": "Bridge Request Metadata", + "description": "Metadata required in a request message forwarded on by the Bridge", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + }, + "source": { + "title": "Bridge Source identifier", + "description": "Field that represents the source application that the request was received from, or the source Desktop Agent if it issued the request itself. The Desktop Agent identifier MUST be set by the bridge.", + "$ref": "common.schema.json#/$defs/BridgeParticipantIdentifier" + }, + "destination": { + "title": "Destination identifier", + "description": "Optional field that represents the destination that the request should be routed to. Must be set by the Desktop Agent for API calls that include a target app parameter and must include the name of the Desktop Agent hosting the target application.", + "$ref": "common.schema.json#/$defs/BridgeParticipantIdentifier" + } + }, + "required": ["requestUuid", "timestamp", "source"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeResponse.schema.json new file mode 100644 index 00000000..b05c84fa --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeResponse.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/bridgeResponse.schema.json", + "title": "Bridge Response Message", + "type": "object", + "description": "A response message from the Bridge back to the original Desktop Agent that raised the request.", + "properties": { + "type": { + "title": "Response Message Type", + "type": "string", + "description": "Identifies the type of the message and it is typically set to the FDC3 function name that the message relates to, e.g. 'findIntent', with 'Response' appended." + }, + "payload": { + "title": "Response Message Payload", + "type": "object", + "description": "The message payload typically contains return values for FDC3 API functions." + }, + "meta": { + "$ref": "#/$defs/BridgeResponseMeta" + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false, + "$defs": { + "BridgeResponseMeta": { + "title": "Bridge Response Metadata", + "description": "Metadata required in a response message collated and/or forwarded on by the Bridge", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + }, + "sources": { + "$ref": "common.schema.json#/$defs/BridgeResponseSources" + }, + "errorSources": { + "$ref": "common.schema.json#/$defs/BridgeResponseErrorSources" + }, + "errorDetails": { + "$ref": "common.schema.json#/$defs/BridgeResponseErrorDetails" + } + }, + "required": ["requestUuid", "responseUuid", "timestamp"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastAgentRequest.schema.json new file mode 100644 index 00000000..24aedc19 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastAgentRequest.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/broadcastAgentRequest.schema.json", + "title": "Broadcast Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/BroadcastRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "BroadcastRequestBase": { + "title": "Broadcast Request", + "type":"object", + "description": "A request to broadcast context on a channel.", + "properties": { + "type": { + "$ref": "../api/broadcastRequest.schema.json#/$defs/BroadcastRequestType" + }, + "payload": { + "$ref": "../api/broadcastRequest.schema.json#/$defs/BroadcastRequestPayload" + }, + "meta": { + "type": "object", + "title": "broadcast request metadata", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + } + }, + "required": ["source"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastBridgeRequest.schema.json new file mode 100644 index 00000000..0ef5401a --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/broadcastBridgeRequest.schema.json", + "title": "Broadcast Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "broadcastAgentRequest.schema.json#/$defs/BroadcastRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/common.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/common.schema.json new file mode 100644 index 00000000..86846c71 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/common.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/common.schema.json", + "title": "Bridge Common definitions", + "type": "object", + "description": "Common definitions that are referenced only in the Bridging wire protocol schemas", + "$defs": { + "RequestSource": { + "title": "Source identifier", + "description": "Field that represents the source application that a request or response was received from, or the source Desktop Agent if it issued the request or response itself.", + "oneOf": [ + { + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + }, + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + } + ] + }, + "AppRequestSource": { + "title": "App Source identifier", + "description": "Field that represents the source application that a request or response was received from.", + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + }, + "AgentDestination": { + "title": "Agent Destination identifier", + "description": "Field that represents a destination Desktop Agent that a request is to be sent to.", + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + "AppDestination": { + "title": "App Destination identifier", + "description": "Field that represents a destination App on a remote Desktop Agent that a request is to be sent to.", + "allOf": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + { + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + } + ] + }, + "BridgeParticipantIdentifier": { + "title": "Bridge Participant Identifier", + "description": "Represents identifiers that MUST include the Desktop Agent name and MAY identify a specific app or instance.", + "oneOf": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + { + "allOf": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + { + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + } + ] + } + ] + }, + "BridgeResponseSources": { + "title": "Desktop Agents that responded", + "type": "array", + "items": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + } + ], + "description": "Array of DesktopAgentIdentifiers for the sources that generated responses to the request. Will contain a single value for individual responses and multiple values for responses that were collated by the bridge. May be omitted if all sources errored. MUST include the `desktopAgent` field when returned by the bridge." + }, + "BridgeResponseErrorSources": { + "title": "Desktop Agents that errored", + "type": "array", + "items": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + } + ], + "description": "Array of DesktopAgentIdentifiers for responses that were not returned to the bridge before the timeout or because an error occurred. May be omitted if all sources responded without errors. MUST include the `desktopAgent` field when returned by the bridge." + }, + "BridgeResponseErrorDetails": { + "title": "Response Error Details", + "type": "array", + "items": { + "$ref": "../api/common.schema.json#/$defs/ErrorMessages" + }, + "description": "Array of error message strings for responses that were not returned to the bridge before the timeout or because an error occurred. Should be the same length as the `errorSources` array and ordered the same. May be omitted if all sources responded without errors." + }, + "DesktopAgentImplementationMetadata": { + "description": "Includes the name assigned to the Desktop Agent by the Bridge.", + "title": "DesktopAgentImplementationMetadata", + "type": "object", + "allOf": [ + { + "$ref": "../api/api.schema.json#/definitions/BaseImplementationMetadata" + }, + { + "type": "object", + "properties": { + "desktopAgent": { + "description": "Used in Desktop Agent Bridging to attribute or target a message to a particular Desktop Agent.", + "type": "string", + "title": "desktopAgent" + } + } + } + ], + "properties": { + "fdc3Version": true, + "provider": true, + "providerVersion": true, + "optionalFeatures": true, + "desktopAgent": true + }, + "required": [ + "fdc3Version", + "optionalFeatures", + "provider", + "desktopAgent" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep.schema.json new file mode 100644 index 00000000..03d55857 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep.schema.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/connectionStep.schema.json", + "title": "Connection Step Message", + "type": "object", + "description": "A message used during the connection flow for a Desktop Agent to the Bridge. Used for messages sent in either direction.", + "properties": { + "type": { + "title": "Connection Step Message type", + "type": "string", + "enum": [ + "hello", + "handshake", + "authenticationFailed", + "connectedAgentsUpdate" + ], + "description": "Identifies the type of the connection step message." + }, + "payload": { + "title": "Message payload", + "type": "object", + "description": "The message payload, containing data pertaining to this connection step.", + "unevaluatedProperties": false + }, + "meta": { + "$ref": "#/$defs/ConnectionStepMeta" + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false, + "$defs": { + "ConnectionStepMeta": { + "title": "Connection Step Metadata", + "description": "Metadata for this connection step message.", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + } + }, + "required": ["timestamp"], + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep2Hello.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep2Hello.schema.json new file mode 100644 index 00000000..82ada01e --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep2Hello.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/connectionStep2Hello.schema.json", + "title": "ConnectionStep2Hello", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/ConnectionStep2HelloBase" + }, + { + "$ref": "connectionStep.schema.json" + } + ], + "$defs": { + "ConnectionStep2HelloBase": { + "type":"object", + "title": "ConnectionStep2Hello", + "description": "Hello message sent by the Bridge to anyone connecting to the Bridge (enables identification as a bridge and confirmation of whether authentication is required)", + "properties": { + "type": { + "title": "Connection Step 2 Message Type", + "const": "hello" + }, + "payload": { + "title": "Connection Step 2 Payload", + "type": "object", + "properties": { + "desktopAgentBridgeVersion": { + "title": "Desktop Agent Bridge Version Number", + "description": "The version of the Bridge", + "type": "string" + }, + "supportedFDC3Versions": { + "title": "Supported FDC3 Versions", + "type": "array", + "description": "The FDC3 versions supported by the Bridge", + "items": { + "type": "string" + } + }, + "authRequired": { + "title": "Authentication Required", + "type": "boolean", + "description": "A flag indicating whether the Desktop Agent Bridge requires authentication or not." + }, + "authToken": { + "title": "Authentication Token", + "type": "string", + "description": "An optional Desktop Agent Bridge JWT authentication token if the Desktop Agent want to authenticate a bridge." + } + }, + "additionalProperties": false, + "required": ["desktopAgentBridgeVersion", "supportedFDC3Versions", "authRequired"] + }, + "meta": { + "title": "Connection Step 2 Metadata", + "type": "object", + "properties": { + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + } + }, + "additionalProperties": false, + "required": ["timestamp"] + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep3Handshake.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep3Handshake.schema.json new file mode 100644 index 00000000..408a60f8 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep3Handshake.schema.json @@ -0,0 +1,105 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/connectionStep3Handshake.schema.json", + "title": "ConnectionStep3Handshake", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/ConnectionStep3HandshakeBase" + }, + { + "$ref": "connectionStep.schema.json" + } + ], + "$defs": { + "ConnectionStep3HandshakeBase": { + "type": "object", + "title": "ConnectionStep3Handshake", + "description": "Handshake message sent by the Desktop Agent to the Bridge (including requested name, channel state and authentication data)", + "properties": { + "type": { + "title": "Connection Step 3 Message Type", + "const": "handshake" + }, + "payload": { + "title": "Connection Step 3 Payload", + "type": "object", + "properties": { + "implementationMetadata": { + "title": "Connecting Agent ImplementationMetadata", + "description": "Desktop Agent ImplementationMetadata trying to connect to the bridge.", + "type": "object", + "allOf": [ + { + "$ref": "../api/api.schema.json#/definitions/BaseImplementationMetadata" + } + ], + "properties": { + "fdc3Version": true, + "provider": true, + "providerVersion": true, + "optionalFeatures": true + }, + "required": [ + "fdc3Version", + "optionalFeatures", + "provider" + ], + "additionalProperties": false + }, + "requestedName": { + "title": "Requested name", + "description": "The requested Desktop Agent name", + "type": "string" + }, + "channelsState": { + "title": "Channel State", + "type": "object", + "description": "The current state of the Desktop Agent's App and User channels (exclude any Private channels), as a mapping of channel id to an array of Context objects, one per type found in the channel, most recent first.", + "additionalProperties": { + "title": "Channel ", + "type": "array", + "items": { + "$ref": "../context/context.schema.json" + } + } + }, + "authToken": { + "title": "Authentication Token", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "implementationMetadata", + "requestedName", + "channelsState" + ] + }, + "meta": { + "title": "Connection Step 3 Metadata", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + } + }, + "additionalProperties": false, + "required": [ + "requestUuid", + "timestamp" + ] + } + }, + "required": [ + "type", + "payload", + "meta" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep4AuthenticationFailed.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep4AuthenticationFailed.schema.json new file mode 100644 index 00000000..20b03d0c --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep4AuthenticationFailed.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/connectionStep4AuthenticationFailed.schema.json", + "title": "ConnectionStep4AuthenticationFailed", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/ConnectionStep4AuthenticationFailedBase" + }, + { + "$ref": "connectionStep.schema.json" + } + ], + "$defs": { + "ConnectionStep4AuthenticationFailedBase": { + "type":"object", + "title": "ConnectionStep4AuthenticationFailed", + "description": "Message sent by Bridge to Desktop Agent if their authentication fails.", + "properties": { + "type": { + "title": "Connection Step 4 Message Type", + "const": "authenticationFailed" + }, + "payload": { + "title": "Connection Step 4 Payload", + "type": "object", + "properties": { + "message": { + "title": "Authentication failed message", + "type": "string" + } + }, + "additionalProperties": false + }, + "meta": { + "title": "Connection Step 4 Metadata", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + } + }, + "additionalProperties": false, + "required": ["requestUuid", "responseUuid", "timestamp"] + } + }, + "required": ["type", "meta"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep6ConnectedAgentsUpdate.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep6ConnectedAgentsUpdate.schema.json new file mode 100644 index 00000000..5693ec01 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep6ConnectedAgentsUpdate.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/connectionStep6ConnectedAgentsUpdate.schema.json", + "title": "ConnectionStep6ConnectedAgentsUpdate", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/ConnectionStep6ConnectedAgentsUpdateBase" + }, + { + "$ref": "connectionStep.schema.json" + } + ], + "$defs": { + "ConnectionStep6ConnectedAgentsUpdateBase": { + "type":"object", + "title": "ConnectionStep6ConnectedAgentsUpdateBase", + "description": "Message sent by Bridge to all Desktop Agent when an agent joins or leaves the bridge, includes the details of all agents, the change made and the expected channel state for all agents.", + "properties": { + "type": { + "title": "Connection Step 6 Message Type", + "const": "connectedAgentsUpdate" + }, + "payload": { + "title": "Connection Step 6 Payload", + "type": "object", + "properties": { + "addAgent": { + "title": "Agents To Add", + "type": "string", + "description": "Should be set when an agent first connects to the bridge and provide its assigned name." + }, + "removeAgent": { + "title": "Agents To Remove", + "type": "string", + "description": "Should be set when an agent disconnects from the bridge and provide the name that no longer is assigned." + }, + "allAgents": { + "title": "All Connected Agents", + "type": "array", + "description": "Desktop Agent Bridge implementation metadata of all connected agents.", + "items": { + "$ref": "common.schema.json#/$defs/DesktopAgentImplementationMetadata" + } + }, + "channelsState": { + "title": "Channel State", + "type": "object", + "description": "The updated state of channels that should be adopted by the agents. Should only be set when an agent is connecting to the bridge.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "../context/context.schema.json" + } + } + } + }, + "additionalProperties": false, + "required": ["allAgents"] + }, + "meta": { + "title": "Connection Step 6 Metadata", + "type": "object", + "properties": { + "requestUuid": { + "$ref": "../api/common.schema.json#/$defs/RequestUuid" + }, + "responseUuid": { + "$ref": "../api/common.schema.json#/$defs/ResponseUuid" + }, + "timestamp": { + "$ref": "../api/common.schema.json#/$defs/Timestamp" + } + }, + "additionalProperties": false, + "required": ["requestUuid", "responseUuid", "timestamp"] + } + }, + "required": ["type", "payload", "meta"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentErrorResponse.schema.json new file mode 100644 index 00000000..b703c106 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesAgentErrorResponse.schema.json", + "title": "FindInstances Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindInstancesErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "FindInstancesErrorResponseBase": { + "title": "FindInstances Error Response", + "type": "object", + "description": "A response to a findInstances request that contains an error.", + "properties": { + "type": { + "$ref": "../api/findInstancesResponse.schema.json#/$defs/FindInstancesResponseType" + }, + "payload": { + "$ref": "../api/findInstancesResponse.schema.json#/$defs/FindInstancesErrorResponsePayload" + }, + "meta": { + "title": "FindInstances Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentRequest.schema.json new file mode 100644 index 00000000..4d9d1934 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentRequest.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesAgentRequest.schema.json", + "title": "FindInstances Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindInstancesRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "FindInstancesRequestBase": { + "title": "FindInstances Request", + "type": "object", + "description": "A request for details of instances of a particular app", + "properties":{ + "type": { + "$ref": "../api/findInstancesRequest.schema.json#/$defs/FindInstancesRequestType" + }, + "payload": { + "$ref": "../api/findInstancesRequest.schema.json#/$defs/FindInstancesRequestPayload" + }, + "meta": { + "title": "FindInstances request metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "destination": { + "$ref": "common.schema.json#/$defs/AgentDestination" + }, + "source": { + "oneOf": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + { + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + } + ] + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentResponse.schema.json new file mode 100644 index 00000000..87518e93 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentResponse.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesAgentResponse.schema.json", + "title": "FindInstances Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindInstancesResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "FindInstancesResponseBase": { + "title": "FindInstances Response", + "type": "object", + "description": "A response to a findInstances request.", + "properties": { + "type": { + "$ref": "../api/findInstancesResponse.schema.json#/$defs/FindInstancesResponseType" + }, + "payload": { + "$ref": "../api/findInstancesResponse.schema.json#/$defs/FindInstancesSuccessResponsePayload" + }, + "meta": true + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeErrorResponse.schema.json new file mode 100644 index 00000000..705c9f62 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesBridgeErrorResponse.schema.json", + "title": "FindInstances Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "findInstancesAgentErrorResponse.schema.json#/$defs/FindInstancesErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeRequest.schema.json new file mode 100644 index 00000000..9b27a2e1 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesBridgeRequest.schema.json", + "title": "FindInstances Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "findInstancesAgentRequest.schema.json#/$defs/FindInstancesRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeResponse.schema.json new file mode 100644 index 00000000..17d39884 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findInstancesBridgeResponse.schema.json", + "title": "FindInstances Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "findInstancesAgentResponse.schema.json#/$defs/FindInstancesResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentErrorResponse.schema.json new file mode 100644 index 00000000..7c2e3e89 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentAgentErrorResponse.schema.json", + "title": "FindIntent Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "FindIntentErrorResponseBase": { + "title": "FindIntent Error Response", + "type": "object", + "description": "A response to a findIntent request that contains an error.", + "properties": { + "type": { + "$ref": "../api/findIntentResponse.schema.json#/$defs/FindIntentResponseType" + }, + "payload": { + "$ref": "../api/findIntentResponse.schema.json#/$defs/FindIntentErrorResponsePayload" + }, + "meta": { + "title": "FindIntent Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentRequest.schema.json new file mode 100644 index 00000000..770b3ef7 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentRequest.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentAgentRequest.schema.json", + "title": "FindIntent Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "FindIntentRequestBase": { + "title": "FindIntent Request", + "type": "object", + "description": "A request for details of apps available to resolve a particular intent and context pair.", + "properties": { + "type": { + "$ref": "../api/findIntentRequest.schema.json#/$defs/FindIntentRequestType" + }, + "payload": { + "$ref": "../api/findIntentRequest.schema.json#/$defs/FindIntentRequestPayload" + }, + "meta": { + "title" : "FindIntent Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": true + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentResponse.schema.json new file mode 100644 index 00000000..9f3b4685 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentAgentResponse.schema.json", + "title": "FindIntent Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "FindIntentResponseBase": { + "title": "FindIntent Response", + "type": "object", + "description": "A response to a findIntent request.", + "properties": { + "type": { + "$ref": "../api/findIntentResponse.schema.json#/$defs/FindIntentResponseType" + }, + "payload": { + "$ref": "../api/findIntentResponse.schema.json#/$defs/FindIntentSuccessResponsePayload" + }, + "meta": { + "title": "FindIntent Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeErrorResponse.schema.json new file mode 100644 index 00000000..76efa543 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeErrorResponse.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentBridgeErrorResponse.schema.json", + "title": "FindIntent Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "findIntentAgentErrorResponse.schema.json#/$defs/FindIntentErrorResponseBase" + }, + + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeRequest.schema.json new file mode 100644 index 00000000..8903f567 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentBridgeRequest.schema.json", + "title": "FindIntent Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "findIntentAgentRequest.schema.json#/$defs/FindIntentRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeResponse.schema.json new file mode 100644 index 00000000..43655a40 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeResponse.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentBridgeResponse.schema.json", + "title": "FindIntent Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "findIntentAgentResponse.schema.json#/$defs/FindIntentResponseBase" + }, + + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentErrorResponse.schema.json new file mode 100644 index 00000000..8270d035 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextAgentErrorResponse.schema.json", + "title": "FindIntentsByContext Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentsByContextErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "FindIntentsByContextErrorResponseBase": { + "title": "FindIntentsByContext Error Response", + "type": "object", + "description": "A response to a findIntentsByContext request that contains an error.", + "properties": { + "type": { + "$ref": "../api/findIntentsByContextResponse.schema.json#/$defs/FindIntentsByContextResponseType" + }, + "payload": { + "$ref": "../api/findIntentsByContextResponse.schema.json#/$defs/FindIntentsByContextErrorResponsePayload" + }, + "meta": { + "title": "FindIntentsByContext Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentRequest.schema.json new file mode 100644 index 00000000..5dacbb41 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentRequest.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextAgentRequest.schema.json", + "title": "FindIntentsByContext Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentsByContextRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "FindIntentsByContextRequestBase": { + "title": "FindIntentsByContext Request", + "type": "object", + "description": "A request for details of intents and apps available to resolve them for a particular context.", + "properties": { + "type": { + "$ref": "../api/findIntentsByContextRequest.schema.json#/$defs/FindIntentsByContextRequestType" + }, + "payload": { + "$ref": "../api/findIntentsByContextRequest.schema.json#/$defs/FindIntentsByContextRequestPayload" + }, + "meta": { + "title": "FindIntentsByContext Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentResponse.schema.json new file mode 100644 index 00000000..99f5f04a --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextAgentResponse.schema.json", + "title": "FindIntentsByContext Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/FindIntentsByContextResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "FindIntentsByContextResponseBase": { + "title": "FindIntentsByContext Response", + "type": "object", + "description": "A response to a findIntentsByContext request.", + "properties": { + "type": { + "$ref": "../api/findIntentsByContextResponse.schema.json#/$defs/FindIntentsByContextResponseType" + }, + "payload": { + "$ref": "../api/findIntentsByContextResponse.schema.json#/$defs/FindIntentsByContextSuccessResponsePayload" + }, + "meta": { + "title": "FindIntentsByContext Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeErrorResponse.schema.json new file mode 100644 index 00000000..045d2c6c --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextBridgeErrorResponse.schema.json", + "title": "FindIntentsByContext Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "findIntentsByContextAgentErrorResponse.schema.json#/$defs/FindIntentsByContextErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeRequest.schema.json new file mode 100644 index 00000000..61ad738d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextBridgeRequest.schema.json", + "title": "FindIntentsByContext Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "findIntentsByContextAgentRequest.schema.json#/$defs/FindIntentsByContextRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeResponse.schema.json new file mode 100644 index 00000000..edb5c068 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/findIntentsByContextBridgeResponse.schema.json", + "title": "FindIntentsByContext Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "findIntentsByContextAgentResponse.schema.json#/$defs/FindIntentsByContextResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentErrorResponse.schema.json new file mode 100644 index 00000000..03cb59ab --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataAgentErrorResponse.schema.json", + "title": "GetAppMetadata Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/GetAppMetadataErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "GetAppMetadataErrorResponseBase": { + "title": "GetAppMetadata Error Response", + "type": "object", + "description": "A response to a getAppMetadata request that contains an error.", + "properties": { + "type": { + "$ref": "../api/getAppMetadataResponse.schema.json#/$defs/GetAppMetadataResponseType" + }, + "payload": { + "$ref": "../api/getAppMetadataResponse.schema.json#/$defs/GetAppMetadataErrorResponsePayload" + }, + "meta": { + "title": "GetAppMetadata Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentRequest.schema.json new file mode 100644 index 00000000..76f2476b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentRequest.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataAgentRequest.schema.json", + "title": "GetAppMetadata Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/GetAppMetadataRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "GetAppMetadataRequestBase": { + "title": "GetAppMetadata Request", + "type": "object", + "description": "A request for metadata about an app", + "properties": { + "type": { + "$ref": "../api/getAppMetadataRequest.schema.json#/$defs/GetAppMetadataRequestType" + }, + "payload": { + "type": "object", + "allOf": [{ + "properties": { + "app": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "required": [ + "app" + ] + },{ + "$ref": "../api/getAppMetadataRequest.schema.json#/$defs/GetAppMetadataRequestPayload" + }] + }, + "meta": { + "title" : "GetAppMetadata Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "destination": { + "$ref": "common.schema.json#/$defs/AgentDestination" + }, + "source": { + "$ref": "common.schema.json#/$defs/RequestSource" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentResponse.schema.json new file mode 100644 index 00000000..549b0e97 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataAgentResponse.schema.json", + "title": "GetAppMetadata Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/GetAppMetadataResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "GetAppMetadataResponseBase": { + "title": "GetAppMetadata Response", + "type": "object", + "description": "A response to a getAppMetadata request.", + "properties": { + "type": { + "$ref": "../api/getAppMetadataResponse.schema.json#/$defs/GetAppMetadataResponseType" + }, + "payload": { + "$ref": "../api/getAppMetadataResponse.schema.json#/$defs/GetAppMetadataSuccessResponsePayload" + }, + "meta": { + "title": "GetAppMetadata Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeErrorResponse.schema.json new file mode 100644 index 00000000..14c3b1e7 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataBridgeErrorResponse.schema.json", + "title": "GetAppMetadata Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "getAppMetadataAgentErrorResponse.schema.json#/$defs/GetAppMetadataErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeRequest.schema.json new file mode 100644 index 00000000..a5bccc3d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataBridgeRequest.schema.json", + "title": "GetAppMetadata Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "getAppMetadataAgentRequest.schema.json#/$defs/GetAppMetadataRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeResponse.schema.json new file mode 100644 index 00000000..2b883d7c --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/getAppMetadataBridgeResponse.schema.json", + "title": "GetAppMetadata Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "getAppMetadataAgentResponse.schema.json#/$defs/GetAppMetadataResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentErrorResponse.schema.json new file mode 100644 index 00000000..5be7ccf2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openAgentErrorResponse.schema.json", + "title": "Open Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/OpenErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "OpenErrorResponseBase": { + "title": "Open Error Response", + "type": "object", + "description": "A response to an open request that contains an error", + "properties": { + "type": { + "$ref": "../api/openResponse.schema.json#/$defs/OpenResponseType" + }, + "payload": { + "$ref": "../api/openResponse.schema.json#/$defs/OpenErrorResponsePayload" + }, + "meta": { + "title": "Open Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentRequest.schema.json new file mode 100644 index 00000000..ef308dfc --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentRequest.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openAgentRequest.schema.json", + "title": "Open Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/OpenRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "OpenRequestBase": { + "title": "Open Request", + "type": "object", + "description": "A request to open an application", + "properties": { + "type": { + "$ref": "../api/openRequest.schema.json#/$defs/OpenRequestType" + }, + "payload": { + "title": "Open Request Payload", + "type": "object", + "properties": { + "app": { + "type": "object", + "title": "App to open", + "description": "The application to open on the specified Desktop Agent", + "allOf": [ + { + "$ref": "../api/api.schema.json#/definitions/DesktopAgentIdentifier" + }, + { + "$ref": "../api/api.schema.json#/definitions/AppIdentifier" + } + ] + }, + "context": { + "$ref": "../context/context.schema.json" + } + }, + "required": ["app"], + "additionalProperties": false + }, + "meta": { + "title": "Open Request Metadata", + "properties": { + "requestUuid": true, + "timestamp": true, + "destination": { + "$ref": "common.schema.json#/$defs/AgentDestination" + }, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + } + }, + "required": ["source"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentResponse.schema.json new file mode 100644 index 00000000..c7116178 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openAgentResponse.schema.json", + "title": "Open Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/OpenResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "OpenResponseBase": { + "title": "Open Response", + "type": "object", + "description": "A response to an open request", + "properties": { + "type": { + "$ref": "../api/openResponse.schema.json#/$defs/OpenResponseType" + }, + "payload": { + "$ref": "../api/openResponse.schema.json#/$defs/OpenSuccessResponsePayload" + }, + "meta": { + "title": "Open Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeErrorResponse.schema.json new file mode 100644 index 00000000..7668e8ce --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openBridgeErrorResponse.schema.json", + "title": "Open Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "openAgentErrorResponse.schema.json#/$defs/OpenErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeRequest.schema.json new file mode 100644 index 00000000..55ec1515 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openBridgeRequest.schema.json", + "title": "Open Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "openAgentRequest.schema.json#/$defs/OpenRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeResponse.schema.json new file mode 100644 index 00000000..8d69b1d5 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/openBridgeResponse.schema.json", + "title": "Open Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "openAgentResponse.schema.json#/$defs/OpenResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastAgentRequest.schema.json new file mode 100644 index 00000000..16c4ca25 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastAgentRequest.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelBroadcastAgentRequest.schema.json", + "title": "PrivateChannelBroadcast Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelBroadcastRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelBroadcastRequestBase": { + "title": "PrivateChannelBroadcast Request", + "type": "object", + "description": "A request to broadcast on a PrivateChannel.", + "properties": { + "type": { + "title": "Private Channel Broadcast Message type", + "const": "PrivateChannel.broadcast" + }, + "payload": { + "title": "PrivateChannelBroadcast Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The Id of the PrivateChannel that the broadcast was sent on" + }, + "context": { + "$ref": "../context/context.schema.json", + "title": "Context", + "description": "The context object that was the payload of a broadcast message." + } + }, + "additionalProperties": false, + "required": ["channelId", "context"] + }, + "meta": { + "title": "PrivateChannelBroadcast Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastBridgeRequest.schema.json new file mode 100644 index 00000000..38bbd4f4 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelBroadcastBridgeRequest.schema.json", + "title": "PrivateChannelBroadcast Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelBroadcastAgentRequest.schema.json#/$defs/PrivateChannelBroadcastRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedAgentRequest.schema.json new file mode 100644 index 00000000..6158231e --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedAgentRequest.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelEventListenerAddedAgentRequest.schema.json", + "title": "PrivateChannelEventListenerAdded Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelEventListenerAddedRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelEventListenerAddedRequestBase": { + "title": "PrivateChannelEventListenerAdded Request", + "type": "object", + "description": "A request to forward on an EventListenerAdded event, relating to a PrivateChannel", + "properties": { + "type": { + "title": "Private Channel EventListenerAdded Message type", + "const": "PrivateChannel.eventListenerAdded" + }, + "payload": { + "title": "PrivateChannelEventListenerAdded Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The id of the PrivateChannel that the event listener was added to." + }, + "listenerType": { + "$ref": "../api/api.schema.json#/definitions/PrivateChannelEventType" + } + }, + "additionalProperties": false, + "required": ["channelId", "listenerType"] + }, + "meta": { + "title": "PrivateChannelEventListenerAdded Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedBridgeRequest.schema.json new file mode 100644 index 00000000..31993207 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelEventListenerAddedBridgeRequest.schema.json", + "title": "PrivateChannelEventListenerAdded Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelEventListenerAddedAgentRequest.schema.json#/$defs/PrivateChannelEventListenerAddedRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedAgentRequest.schema.json new file mode 100644 index 00000000..3a685401 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedAgentRequest.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelEventListenerRemovedAgentRequest.schema.json", + "title": "PrivateChannelEventListenerRemoved Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelEventListenerRemovedRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelEventListenerRemovedRequestBase": { + "title": "PrivateChannelEventListenerRemoved Request", + "type": "object", + "description": "A request to forward on an EventListenerRemoved event, relating to a PrivateChannel", + "properties": { + "type": { + "title": "Private Channel EventListenerRemoved Message type", + "const": "PrivateChannel.eventListenerRemoved" + }, + "payload": { + "title": "PrivateChannelEventListenerRemoved Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The id of the PrivateChannel that the event listener was removed from." + }, + "listenerType": { + "$ref": "../api/api.schema.json#/definitions/PrivateChannelEventType" + } + }, + "additionalProperties": false, + "required": ["channelId", "listenerType"] + }, + "meta": { + "title": "PrivateChannelEventListenerRemoved Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedBridgeRequest.schema.json new file mode 100644 index 00000000..e41b1e1b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelEventListenerRemovedBridgeRequest.schema.json", + "title": "PrivateChannelEventListenerRemoved Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelEventListenerRemovedAgentRequest.schema.json#/$defs/PrivateChannelEventListenerRemovedRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerAgentRequest.schema.json new file mode 100644 index 00000000..2fe47c66 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerAgentRequest.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnAddContextListenerAgentRequest.schema.json", + "title": "PrivateChannelOnAddContextListener Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelOnAddContextListenerRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelOnAddContextListenerRequestBase": { + "title": "PrivateChannelOnAddContextListener Request", + "type": "object", + "description": "A request to forward on an AddContextListener event, relating to a PrivateChannel", + "properties": { + "type": { + "title": "Private Channel OnAddContextListener Message type", + "const": "PrivateChannel.onAddContextListener" + }, + "payload": { + "title": "PrivateChannelOnAddContextListener Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The id of the PrivateChannel that the context listener was added to." + }, + "contextType": { + "oneOf": [ + { "type": "string" }, + { "type": "null" } + ], + "title": "Context Type", + "description": "The type of the context listener added. Should be null for an untyped listener." + } + }, + "additionalProperties": false, + "required": ["channelId", "contextType"] + }, + "meta": { + "title": "PrivateChannelOnAddContextListener Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerBridgeRequest.schema.json new file mode 100644 index 00000000..c8f09348 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnAddContextListenerBridgeRequest.schema.json", + "title": "PrivateChannelOnAddContextListener Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelOnAddContextListenerAgentRequest.schema.json#/$defs/PrivateChannelOnAddContextListenerRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectAgentRequest.schema.json new file mode 100644 index 00000000..540f6ddf --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectAgentRequest.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnDisconnectAgentRequest.schema.json", + "title": "PrivateChannelOnDisconnect Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelOnDisconnectRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelOnDisconnectRequestBase": { + "title": "PrivateChannelOnDisconnect Request", + "type": "object", + "description": "A request to forward on a Disconnect event, relating to a PrivateChannel", + "properties": { + "type": { + "title": "Private Channel OnDisconnect Message type", + "const": "PrivateChannel.onDisconnect" + }, + "payload": { + "title": "PrivateChannelOnDisconnect Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The id of the PrivateChannel that the agent discconnected from." + } + }, + "additionalProperties": false, + "required": ["channelId"] + }, + "meta": { + "title": "PrivateChannelOnDisconnect Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectBridgeRequest.schema.json new file mode 100644 index 00000000..73742515 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnDisconnectBridgeRequest.schema.json", + "title": "PrivateChannelOnDisconnect Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelOnDisconnectAgentRequest.schema.json#/$defs/PrivateChannelOnDisconnectRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeAgentRequest.schema.json new file mode 100644 index 00000000..115c9f90 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeAgentRequest.schema.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnUnsubscribeAgentRequest.schema.json", + "title": "PrivateChannelOnUnsubscribe Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/PrivateChannelOnUnsubscribeRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "PrivateChannelOnUnsubscribeRequestBase": { + "title": "PrivateChannelOnUnsubscribe Request", + "type": "object", + "description": "A request to forward on an Unsubscribe event, relating to a PrivateChannel", + "properties": { + "type": { + "title": "Private Channel OnUnsubscribe Message type", + "const": "PrivateChannel.onUnsubscribe" + }, + "payload": { + "title": "PrivateChannelOnUnsubscribe Request Payload", + "type": "object", + "properties": { + "channelId": { + "type": "string", + "title": "Channel Id", + "description": "The id of the PrivateChannel that the context listener was unsubscribed from." + }, + "contextType": { + "oneOf": [ + { "type": "string" }, + { "type": "null" } + ], + "title": "Context Type", + "description": "The type of the context listener that was unsubscribed. Should be null for an untyped listener." + } + }, + "additionalProperties": false, + "required": ["channelId", "contextType"] + }, + "meta": { + "title": "PrivateChannelOnUnsubscribe Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + }, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "unevaluatedProperties": false + } + } + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeBridgeRequest.schema.json new file mode 100644 index 00000000..5e364e3d --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/privateChannelOnUnsubscribeBridgeRequest.schema.json", + "title": "PrivateChannelOnUnsubscribe Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "privateChannelOnUnsubscribeAgentRequest.schema.json#/$defs/PrivateChannelOnUnsubscribeRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentErrorResponse.schema.json new file mode 100644 index 00000000..9dea12a5 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentErrorResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentAgentErrorResponse.schema.json", + "title": "RaiseIntent Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/RaiseIntentErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "RaiseIntentErrorResponseBase": { + "title": "RaiseIntent Error Response", + "type": "object", + "description": "A response to a request to raise an intent that contains an error.", + "properties": { + "type": { + "$ref": "../api/raiseIntentResponse.schema.json#/$defs/RaiseIntentResponseType" + }, + "payload": { + "$ref": "../api/raiseIntentResponse.schema.json#/$defs/RaiseIntentErrorResponsePayload" + }, + "meta": { + "title": "RaiseIntent Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentRequest.schema.json new file mode 100644 index 00000000..a6ceca45 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentRequest.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentAgentRequest.schema.json", + "title": "RaiseIntent Agent Request", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/RaiseIntentRequestBase" + }, + { + "$ref": "agentRequest.schema.json" + } + ], + "$defs": { + "RaiseIntentRequestBase": { + "title": "RaiseIntent Request", + "type": "object", + "description": "A request to raise an intent.", + "properties": { + "type": { + "$ref": "../api/raiseIntentRequest.schema.json#/$defs/RaiseIntentRequestType" + }, + "payload": { + "title": "RaiseIntent Request Payload", + "type": "object", + "properties": { + "intent": { + "type": "string" + }, + "context": { + "$ref": "../context/context.schema.json" + }, + "app": { + "$ref": "common.schema.json#/$defs/AppDestination" + } + }, + "required": ["intent", "context", "app"], + "additionalProperties": false + }, + "meta": { + "title": "RaiseIntent Request Metadata", + "type": "object", + "properties": { + "requestUuid": true, + "timestamp": true, + "destination": { + "$ref": "common.schema.json#/$defs/AppDestination" + }, + "source": { + "$ref": "common.schema.json#/$defs/AppRequestSource" + } + }, + "unevaluatedProperties": false, + "required": ["requestUuid","timestamp","destination","source"] + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentResponse.schema.json new file mode 100644 index 00000000..8ad8c8b2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentAgentResponse.schema.json", + "title": "RaiseIntent Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/RaiseIntentResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "RaiseIntentResponseBase": { + "title": "RaiseIntent Response", + "type": "object", + "description": "A response to a request to raise an intent.", + "properties": { + "type": { + "$ref": "../api/raiseIntentResponse.schema.json#/$defs/RaiseIntentResponseType" + }, + "payload": { + "$ref": "../api/raiseIntentResponse.schema.json#/$defs/RaiseIntentSuccessResponsePayload" + }, + "meta": { + "title": "RaiseIntent Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeErrorResponse.schema.json new file mode 100644 index 00000000..e01cd351 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentBridgeErrorResponse.schema.json", + "title": "RaiseIntent Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "raiseIntentAgentErrorResponse.schema.json#/$defs/RaiseIntentErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeRequest.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeRequest.schema.json new file mode 100644 index 00000000..6922eae4 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeRequest.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentBridgeRequest.schema.json", + "title": "RaiseIntent Bridge Request", + "type": "object", + "allOf": [ + { + "$ref": "raiseIntentAgentRequest.schema.json#/$defs/RaiseIntentRequestBase" + }, + { + "$ref": "bridgeRequest.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeResponse.schema.json new file mode 100644 index 00000000..4f211665 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentBridgeResponse.schema.json", + "title": "RaiseIntent Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "raiseIntentAgentResponse.schema.json#/$defs/RaiseIntentResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentErrorResponse.schema.json new file mode 100644 index 00000000..d2ec66e4 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentErrorResponse.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentResultAgentErrorResponse.schema.json", + "title": "RaiseIntent Result Agent Error Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/RaiseIntentResultErrorResponseBase" + }, + { + "$ref": "agentErrorResponse.schema.json" + } + ], + "$defs": { + "RaiseIntentResultErrorResponseBase": { + "title": "RaiseIntent Result Error Response", + "type": "object", + "description": "A secondary response to a request to raise an intent used to deliver the intent result, which contains an error", + "properties": { + "type": { + "title": "RaiseIntent Result Response Message type", + "const": "raiseIntentResultResponse" + }, + "payload": { + "title": "RaiseIntent Result Error Response Payload", + "type": "object", + "properties": { + "error": { + "title": "RaiseIntent Result Error Message", + "oneOf": [ + { "$ref": "../api/api.schema.json#/definitions/ResultError" }, + { "$ref": "../api/api.schema.json#/definitions/BridgingError" } + ] + } + }, + "required": ["error"], + "additionalProperties": false + }, + "meta": { + "title": "RaiseIntent Result Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentResponse.schema.json new file mode 100644 index 00000000..f97331b2 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentResponse.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentResultAgentResponse.schema.json", + "title": "RaiseIntent Result Agent Response", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/RaiseIntentResultResponseBase" + }, + { + "$ref": "agentResponse.schema.json" + } + ], + "$defs": { + "RaiseIntentResultResponseBase": { + "title": "RaiseIntent Result Response", + "type": "object", + "description": "A secondary response to a request to raise an intent used to deliver the intent result", + "properties": { + "type": { + "$ref": "../api/raiseIntentResultResponse.schema.json#/$defs/RaiseIntentResultResponseType" + }, + "payload": { + "$ref": "../api/raiseIntentResultResponse.schema.json#/$defs/RaiseIntentResultSuccessResponsePayload" + }, + "meta": { + "title": "RaiseIntent Result Response Metadata", + "type": "object" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeErrorResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeErrorResponse.schema.json new file mode 100644 index 00000000..280475f3 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeErrorResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentResultBridgeErrorResponse.schema.json", + "title": "RaiseIntent Result Bridge Error Response", + "type": "object", + "allOf": [ + { + "$ref": "raiseIntentResultAgentErrorResponse.schema.json#/$defs/RaiseIntentResultErrorResponseBase" + }, + { + "$ref": "bridgeErrorResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeResponse.schema.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeResponse.schema.json new file mode 100644 index 00000000..30c0e680 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeResponse.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://fdc3.finos.org/schemas/next/bridging/raiseIntentResultBridgeResponse.schema.json", + "title": "RaiseIntent Result Bridge Response", + "type": "object", + "allOf": [ + { + "$ref": "raiseIntentResultAgentResponse.schema.json#/$defs/RaiseIntentResultResponseBase" + }, + { + "$ref": "bridgeResponse.schema.json" + } + ] +} diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/README.md b/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/README.md new file mode 100644 index 00000000..f097af5b --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/README.md @@ -0,0 +1,38 @@ +# Agent Bridging AsyncAPI schema + +This folder contains an AsyncAPI schema that may be used to generate clients and server stubs for Desktop Agent Bridging. It is based on references to the JSON schema files that define the various messages in the adjacent schemas/bridging folder. + +Example commands to generate code from the AsyncAPI schema: +(run from the root of your FDC3 checkout) + +First run: + +```ps +npm install -g @asyncapi/generator +``` + +Then: + +- .NET + + ```ps + ag --install schemas/bridgingAsyncAPI/bridgingAsyncAPI.json @asyncapi/dotnet-nats-template -o ../some/path/outside/FDC3/folder + ``` + +- Node.js + + ```ps + ag --install schemas/bridgingAsyncAPI/bridgingAsyncAPI.json @asyncapi/nodejs-ws-template -o ../some/path/outside/FDC3/folder -p server=local + ``` + +- Markdown + + ```ps + ag --install schemas/bridgingAsyncAPI/bridgingAsyncAPI.json @asyncapi/markdown-template -o ../some/path/outside/FDC3/folder + ``` + +- HTML + + ```ps + ag --install schemas/bridgingAsyncAPI/bridgingAsyncAPI.json @asyncapi/html-template -o ../some/path/outside/FDC3/folder + ``` diff --git a/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/bridgingAsyncAPI.json b/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/bridgingAsyncAPI.json new file mode 100644 index 00000000..657a4212 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/bridgingAsyncAPI.json @@ -0,0 +1,407 @@ +{ + "asyncapi": "2.6.0", + "info": { + "title": "Desktop Agent Bridge", + "version": "1.0.0", + "description": "API for an FDC3 Desktop Agent to communicate with an FDC3 Desktop Agent Bridge and through it, other Desktop Agents.", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" + } + }, + "servers": { + "local": { + "url": "ws://localhost:4475", + "description": "Desktop agent bridge server exposing websocket connection", + "protocol": "ws" + } + }, + "defaultContentType": "application/json", + "channels": { + "/": { + "publish": { + "message": { + "oneOf": [ + { + "$ref": "#/components/messages/handshake" + }, + { + "$ref": "#/components/messages/broadcastRequest-Agent" + }, + { + "$ref": "#/components/messages/findInstancesRequest-Agent" + }, + { + "$ref": "#/components/messages/findInstancesResponse-Agent" + }, + { + "$ref": "#/components/messages/findInstancesErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/findIntentRequest-Agent" + }, + { + "$ref": "#/components/messages/findIntentResponse-Agent" + }, + { + "$ref": "#/components/messages/findIntentErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/findIntentsByContextRequest-Agent" + }, + { + "$ref": "#/components/messages/findIntentsByContextResponse-Agent" + }, + { + "$ref": "#/components/messages/findIntentsByContextErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/getAppMetadataRequest-Agent" + }, + { + "$ref": "#/components/messages/getAppMetadataResponse-Agent" + }, + { + "$ref": "#/components/messages/getAppMetadataErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/openRequest-Agent" + }, + { + "$ref": "#/components/messages/openResponse-Agent" + }, + { + "$ref": "#/components/messages/openErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/raiseIntentRequest-Agent" + }, + { + "$ref": "#/components/messages/raiseIntentResponse-Agent" + }, + { + "$ref": "#/components/messages/raiseIntentErrorResponse-Agent" + }, + { + "$ref": "#/components/messages/privateChannelBroadcast-Agent" + }, + { + "$ref": "#/components/messages/privateChannelEventListenerAdded-Agent" + }, + { + "$ref": "#/components/messages/privateChannelEventListenerRemoved-Agent" + }, + { + "$ref": "#/components/messages/privateChannelOnAddContextListener-Agent" + }, + { + "$ref": "#/components/messages/privateChannelOnDisconnect-Agent" + }, + { + "$ref": "#/components/messages/privateChannelOnUnsubscribe-Agent" + }, + { + "$ref": "#/components/messages/privateChannelOnDisconnect-Agent" + }, + { + "$ref": "#/components/messages/raiseIntentResultResponse-Agent" + }, + { + "$ref": "#/components/messages/raiseIntentResultErrorResponse-Agent" + } + ] + }, + "description": "Messages sent by A Desktop Agent to a Bridge", + "operationId": "Send" + }, + "subscribe": { + "message": { + "oneOf": [ + { + "$ref": "#/components/messages/hello" + }, + { + "$ref": "#/components/messages/authenticationFailed" + }, + { + "$ref": "#/components/messages/connectedAgentsUpdate" + }, + { + "$ref": "#/components/messages/findInstancesResponse-Bridge" + }, + { + "$ref": "#/components/messages/findInstancesErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/findIntentResponse-Bridge" + }, + { + "$ref": "#/components/messages/findIntentErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/findIntentsByContextResponse-Bridge" + }, + { + "$ref": "#/components/messages/findIntentsByContextErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/getAppMetadataResponse-Bridge" + }, + { + "$ref": "#/components/messages/getAppMetadataErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/openResponse-Bridge" + }, + { + "$ref": "#/components/messages/openErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/raiseIntentResponse-Bridge" + }, + { + "$ref": "#/components/messages/raiseIntentErrorResponse-Bridge" + }, + { + "$ref": "#/components/messages/raiseIntentResultResponse-Bridge" + }, + { + "$ref": "#/components/messages/raiseIntentResultErrorResponse-Bridge" + } + ] + }, + "description": "Messages sent by a Bridge to a Desktop Agent", + "operationId": "Receive" + } + } + }, + "components": { + "messages": { + "broadcastRequest-Agent": { + "payload": { + "$ref": "../bridging/broadcastAgentRequest.schema.json#" + } + }, + "hello": { + "payload": { + "$ref": "../bridging/connectionStep2Hello.schema.json#" + } + }, + "handshake": { + "payload": { + "$ref": "../bridging/connectionStep3Handshake.schema.json#" + } + }, + "authenticationFailed": { + "payload": { + "$ref": "../bridging/connectionStep4AuthenticationFailed.schema.json#" + } + }, + "connectedAgentsUpdate": { + "payload": { + "$ref": "../bridging/connectionStep6ConnectedAgentsUpdate.schema.json#" + } + }, + "findInstancesRequest-Agent": { + "payload": { + "$ref": "../bridging/findInstancesAgentRequest.schema.json#" + } + }, + "findInstancesResponse-Agent": { + "payload": { + "$ref": "../bridging/findInstancesAgentResponse.schema.json#" + } + }, + "findInstancesErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/findInstancesAgentErrorResponse.schema.json#" + } + }, + "findInstancesResponse-Bridge": { + "payload": { + "$ref": "../bridging/findInstancesBridgeResponse.schema.json#" + } + }, + "findInstancesErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/findInstancesBridgeErrorResponse.schema.json#" + } + }, + "findIntentRequest-Agent": { + "payload": { + "$ref": "../bridging/findIntentAgentRequest.schema.json#" + } + }, + "findIntentResponse-Agent": { + "payload": { + "$ref": "../bridging/findIntentAgentResponse.schema.json#" + } + }, + "findIntentErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/findIntentAgentErrorResponse.schema.json#" + } + }, + "findIntentResponse-Bridge": { + "payload": { + "$ref": "../bridging/findIntentBridgeResponse.schema.json#" + } + }, + "findIntentErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/findIntentBridgeErrorResponse.schema.json#" + } + }, + "findIntentsByContextRequest-Agent": { + "payload": { + "$ref": "../bridging/findIntentsByContextAgentRequest.schema.json#" + } + }, + "findIntentsByContextResponse-Agent": { + "payload": { + "$ref": "../bridging/findIntentsByContextAgentResponse.schema.json#" + } + }, + "findIntentsByContextErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/findIntentsByContextAgentErrorResponse.schema.json#" + } + }, + "findIntentsByContextResponse-Bridge": { + "payload": { + "$ref": "../bridging/findIntentsByContextBridgeResponse.schema.json#" + } + }, + "findIntentsByContextErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/findIntentsByContextBridgeErrorResponse.schema.json#" + } + }, + "getAppMetadataRequest-Agent": { + "payload": { + "$ref": "../bridging/getAppMetadataAgentRequest.schema.json#" + } + }, + "getAppMetadataResponse-Agent": { + "payload": { + "$ref": "../bridging/getAppMetadataAgentResponse.schema.json#" + } + }, + "getAppMetadataErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/getAppMetadataAgentErrorResponse.schema.json#" + } + }, + "getAppMetadataResponse-Bridge": { + "payload": { + "$ref": "../bridging/getAppMetadataBridgeResponse.schema.json#" + } + }, + "getAppMetadataErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/getAppMetadataBridgeErrorResponse.schema.json#" + } + }, + "openRequest-Agent": { + "payload": { + "$ref": "../bridging/openAgentRequest.schema.json#" + } + }, + "openResponse-Agent": { + "payload": { + "$ref": "../bridging/openAgentResponse.schema.json#" + } + }, + "openErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/openAgentErrorResponse.schema.json#" + } + }, + "openResponse-Bridge": { + "payload": { + "$ref": "../bridging/openBridgeResponse.schema.json#" + } + }, + "openErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/openBridgeErrorResponse.schema.json#" + } + }, + "privateChannelBroadcast-Agent": { + "payload": { + "$ref": "../bridging/privateChannelBroadcastAgentRequest.schema.json#" + } + }, + "privateChannelEventListenerAdded-Agent": { + "payload": { + "$ref": "../bridging/privateChannelEventListenerAddedAgentRequest.schema.json#" + } + }, + "privateChannelEventListenerRemoved-Agent": { + "payload": { + "$ref": "../bridging/privateChannelEventListenerRemovedAgentRequest.schema.json#" + } + }, + "privateChannelOnAddContextListener-Agent": { + "payload": { + "$ref": "../bridging/privateChannelOnAddContextListenerAgentRequest.schema.json#" + } + }, + "privateChannelOnDisconnect-Agent": { + "payload": { + "$ref": "../bridging/privateChannelOnDisconnectAgentRequest.schema.json#" + } + }, + "privateChannelOnUnsubscribe-Agent": { + "payload": { + "$ref": "../bridging/privateChannelOnUnsubscribeAgentRequest.schema.json#" + } + }, + "raiseIntentRequest-Agent": { + "payload": { + "$ref": "../bridging/raiseIntentAgentRequest.schema.json#" + } + }, + "raiseIntentResponse-Agent": { + "payload": { + "$ref": "../bridging/raiseIntentAgentResponse.schema.json#" + } + }, + "raiseIntentErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/raiseIntentAgentErrorResponse.schema.json#" + } + }, + "raiseIntentResponse-Bridge": { + "payload": { + "$ref": "../bridging/raiseIntentBridgeResponse.schema.json#" + } + }, + "raiseIntentErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/raiseIntentBridgeErrorResponse.schema.json#" + } + }, + "raiseIntentResultResponse-Agent": { + "payload": { + "$ref": "../bridging/raiseIntentResultAgentResponse.schema.json#" + } + }, + "raiseIntentResultErrorResponse-Agent": { + "payload": { + "$ref": "../bridging/raiseIntentResultAgentErrorResponse.schema.json#" + } + }, + "raiseIntentResultResponse-Bridge": { + "payload": { + "$ref": "../bridging/raiseIntentResultBridgeResponse.schema.json#" + } + }, + "raiseIntentResultErrorResponse-Bridge": { + "payload": { + "$ref": "../bridging/raiseIntentResultBridgeErrorResponse.schema.json#" + } + } + } + } +} \ No newline at end of file diff --git a/fdc3-schema/src/main/schemas-temp/README.md b/fdc3-schema/src/main/schemas-temp/README.md new file mode 100644 index 00000000..179442d3 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/README.md @@ -0,0 +1,3 @@ +Note: this is temporary. Really, schemas should be downloaded from NPM to be consistent with FDC3 Typescript. + +(The code for this is already in pom.xml) \ No newline at end of file diff --git a/fdc3api/pom.xml b/fdc3-standard/pom.xml similarity index 69% rename from fdc3api/pom.xml rename to fdc3-standard/pom.xml index 7a7ee4ff..494fe5d5 100644 --- a/fdc3api/pom.xml +++ b/fdc3-standard/pom.xml @@ -1,7 +1,7 @@ 4.0.0 - com.finos.fdc3.api - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT pom - finos-bmo-hackathon + FDC3 Java API http://maven.apache.org - fdc3api - client - openfin-fdc3-adapter + fdc3-standard + fdc3-schema + fdc3-context + fdc3-agent-proxy + fdc3-get-agent + fdc3-example-app 11 11 + + + + local + + true + + + + + + download + + diff --git a/client/pom.xml b/unused/client/pom.xml similarity index 96% rename from client/pom.xml rename to unused/client/pom.xml index 4bb2da63..452aae95 100644 --- a/client/pom.xml +++ b/unused/client/pom.xml @@ -21,7 +21,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.finos.fdc3.api + org.finos.fdc3.api finos-bmo-hackathon 1.0.0-SNAPSHOT @@ -86,7 +86,7 @@ - com.finos.fdc3.client.launcher.FDC3JavaAPIDemoApplication + org.finos.fdc3.client.launcher.FDC3JavaAPIDemoApplication @@ -113,7 +113,7 @@ ${poi.version} - com.finos.fdc3.api + org.finos.fdc3.api openfin-fdc3-adapter ${project.version} compile diff --git a/client/src/main/java/com/finos/fdc3/client/AppGUIException.java b/unused/client/src/main/java/org/finos/fdc3/client/AppGUIException.java similarity index 96% rename from client/src/main/java/com/finos/fdc3/client/AppGUIException.java rename to unused/client/src/main/java/org/finos/fdc3/client/AppGUIException.java index f0e900ff..66159d0f 100644 --- a/client/src/main/java/com/finos/fdc3/client/AppGUIException.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/AppGUIException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client; +package org.finos.fdc3.client; public class AppGUIException extends RuntimeException { diff --git a/client/src/main/java/com/finos/fdc3/client/AppUI.java b/unused/client/src/main/java/org/finos/fdc3/client/AppUI.java similarity index 95% rename from client/src/main/java/com/finos/fdc3/client/AppUI.java rename to unused/client/src/main/java/org/finos/fdc3/client/AppUI.java index f0e3ee2b..42e8101b 100644 --- a/client/src/main/java/com/finos/fdc3/client/AppUI.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/AppUI.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.client; - -import com.finos.fdc3.client.handler.DataLoadHandler; -import com.finos.fdc3.client.handler.SystemChannelHandler; -import com.finos.fdc3.client.model.IncomingDataTableModel; -import com.finos.fdc3.client.model.SnPTableModel; -import com.finos.fdc3.client.publisher.OrderMessagePublisher; -import com.finos.fdc3.client.model.OrderTableModel; -import com.finos.fdc3.client.utils.SwingUtils; +package org.finos.fdc3.client; + +import org.finos.fdc3.client.handler.DataLoadHandler; +import org.finos.fdc3.client.handler.SystemChannelHandler; +import org.finos.fdc3.client.model.IncomingDataTableModel; +import org.finos.fdc3.client.model.SnPTableModel; +import org.finos.fdc3.client.publisher.OrderMessagePublisher; +import org.finos.fdc3.client.model.OrderTableModel; +import org.finos.fdc3.client.utils.SwingUtils; import javax.swing.JButton; import javax.swing.JComboBox; diff --git a/client/src/main/java/com/finos/fdc3/client/data/IncomingData.java b/unused/client/src/main/java/org/finos/fdc3/client/data/IncomingData.java similarity index 98% rename from client/src/main/java/com/finos/fdc3/client/data/IncomingData.java rename to unused/client/src/main/java/org/finos/fdc3/client/data/IncomingData.java index 162021d8..cd0c389b 100644 --- a/client/src/main/java/com/finos/fdc3/client/data/IncomingData.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/data/IncomingData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.data; +package org.finos.fdc3.client.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/client/src/main/java/com/finos/fdc3/client/data/OrderData.java b/unused/client/src/main/java/org/finos/fdc3/client/data/OrderData.java similarity index 98% rename from client/src/main/java/com/finos/fdc3/client/data/OrderData.java rename to unused/client/src/main/java/org/finos/fdc3/client/data/OrderData.java index d9fcdd60..f8cc5ad6 100644 --- a/client/src/main/java/com/finos/fdc3/client/data/OrderData.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/data/OrderData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.data; +package org.finos.fdc3.client.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/client/src/main/java/com/finos/fdc3/client/data/SnPData.java b/unused/client/src/main/java/org/finos/fdc3/client/data/SnPData.java similarity index 98% rename from client/src/main/java/com/finos/fdc3/client/data/SnPData.java rename to unused/client/src/main/java/org/finos/fdc3/client/data/SnPData.java index c780d425..d58ea4e8 100644 --- a/client/src/main/java/com/finos/fdc3/client/data/SnPData.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/data/SnPData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.data; +package org.finos.fdc3.client.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java b/unused/client/src/main/java/org/finos/fdc3/client/handler/DataLoadHandler.java similarity index 87% rename from client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java rename to unused/client/src/main/java/org/finos/fdc3/client/handler/DataLoadHandler.java index 6f1a7f0f..13d38353 100644 --- a/client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/handler/DataLoadHandler.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.finos.fdc3.client.handler; - -import com.finos.fdc3.client.data.OrderData; -import com.finos.fdc3.client.data.SnPData; -import com.finos.fdc3.client.model.OrderTableModel; -import com.finos.fdc3.client.model.SnPTableModel; -import com.finos.fdc3.client.utils.SampleDataLoaderUtil; -import com.finos.fdc3.client.utils.SwingUtils; +package org.finos.fdc3.client.handler; + +import org.finos.fdc3.client.data.OrderData; +import org.finos.fdc3.client.data.SnPData; +import org.finos.fdc3.client.model.OrderTableModel; +import org.finos.fdc3.client.model.SnPTableModel; +import org.finos.fdc3.client.utils.SampleDataLoaderUtil; +import org.finos.fdc3.client.utils.SwingUtils; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; diff --git a/client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java b/unused/client/src/main/java/org/finos/fdc3/client/handler/SystemChannelHandler.java similarity index 95% rename from client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java rename to unused/client/src/main/java/org/finos/fdc3/client/handler/SystemChannelHandler.java index 7c59e9a4..d62c9cbf 100644 --- a/client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/handler/SystemChannelHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.handler; +package org.finos.fdc3.client.handler; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -28,8 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.api.DesktopAgent; -import com.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.channel.Channel; public class SystemChannelHandler implements ActionListener { diff --git a/client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java b/unused/client/src/main/java/org/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java similarity index 88% rename from client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java rename to unused/client/src/main/java/org/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java index 8d87c59a..1adf65c0 100644 --- a/client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package com.finos.fdc3.client.launcher; +package org.finos.fdc3.client.launcher; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.context.Instrument; -import com.finos.fdc3.api.metadata.ContextMetadata; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.client.data.IncomingData; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.context.Instrument; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.client.data.IncomingData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.adapter.openfin.OpenFinDesktopAgent; -import com.finos.fdc3.adapter.openfin.OpenFinFDC3Config; -import com.finos.fdc3.api.DesktopAgent; -import com.finos.fdc3.client.AppUI; +import org.finos.fdc3.adapter.openfin.OpenFinDesktopAgent; +import org.finos.fdc3.adapter.openfin.OpenFinFDC3Config; +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.client.AppUI; import com.openfin.desktop.RuntimeConfiguration; -import com.finos.fdc3.api.context.Position; +import org.finos.fdc3.api.context.Position; import java.math.BigDecimal; diff --git a/client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java b/unused/client/src/main/java/org/finos/fdc3/client/model/IncomingDataTableModel.java similarity index 93% rename from client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java rename to unused/client/src/main/java/org/finos/fdc3/client/model/IncomingDataTableModel.java index 1187cbe0..30a48897 100644 --- a/client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/model/IncomingDataTableModel.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.finos.fdc3.client.model; +package org.finos.fdc3.client.model; -import com.finos.fdc3.client.data.IncomingData; -import com.finos.fdc3.client.data.OrderData; +import org.finos.fdc3.client.data.IncomingData; +import org.finos.fdc3.client.data.OrderData; import javax.swing.table.AbstractTableModel; import java.util.ArrayList; diff --git a/client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java b/unused/client/src/main/java/org/finos/fdc3/client/model/OrderTableModel.java similarity index 96% rename from client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java rename to unused/client/src/main/java/org/finos/fdc3/client/model/OrderTableModel.java index 9a67e9a9..c6b30f7d 100644 --- a/client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/model/OrderTableModel.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.client.model; +package org.finos.fdc3.client.model; -import com.finos.fdc3.client.data.OrderData; +import org.finos.fdc3.client.data.OrderData; import javax.swing.table.AbstractTableModel; import java.util.ArrayList; diff --git a/client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java b/unused/client/src/main/java/org/finos/fdc3/client/model/SnPTableModel.java similarity index 95% rename from client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java rename to unused/client/src/main/java/org/finos/fdc3/client/model/SnPTableModel.java index 36c1f7e4..bf94ca6e 100644 --- a/client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/model/SnPTableModel.java @@ -15,9 +15,9 @@ */ -package com.finos.fdc3.client.model; +package org.finos.fdc3.client.model; -import com.finos.fdc3.client.data.SnPData; +import org.finos.fdc3.client.data.SnPData; import javax.swing.table.AbstractTableModel; import java.util.ArrayList; diff --git a/client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java b/unused/client/src/main/java/org/finos/fdc3/client/publisher/OrderMessagePublisher.java similarity index 92% rename from client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java rename to unused/client/src/main/java/org/finos/fdc3/client/publisher/OrderMessagePublisher.java index 9fd9c558..c10c9c80 100644 --- a/client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/publisher/OrderMessagePublisher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.publisher; +package org.finos.fdc3.client.publisher; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -23,17 +23,17 @@ import javax.swing.JTable; -import com.finos.fdc3.client.AppUI; -import com.finos.fdc3.client.data.SnPData; +import org.finos.fdc3.client.AppUI; +import org.finos.fdc3.client.data.SnPData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.api.DesktopAgent; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.context.Instrument; -import com.finos.fdc3.api.utils.JacksonUtilities; -import com.finos.fdc3.client.data.OrderData; -import com.finos.fdc3.client.model.OrderTableModel; +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.context.Instrument; +import org.finos.fdc3.api.utils.JacksonUtilities; +import org.finos.fdc3.client.data.OrderData; +import org.finos.fdc3.client.model.OrderTableModel; public class OrderMessagePublisher implements ActionListener { diff --git a/client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java b/unused/client/src/main/java/org/finos/fdc3/client/utils/JSONAdapterUtil.java similarity index 94% rename from client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java rename to unused/client/src/main/java/org/finos/fdc3/client/utils/JSONAdapterUtil.java index ed266f3e..ac859e20 100644 --- a/client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/utils/JSONAdapterUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.utils; +package org.finos.fdc3.client.utils; import com.sun.codemodel.JCodeModel; import org.jsonschema2pojo.*; @@ -30,7 +30,7 @@ public class JSONAdapterUtil public static void main(String[] arg) { try { convertJsonToJavaClass(new File("C:\\finos\\json-schema\\Sample-Json.json").toURI().toURL(), - new File("C:\\finos\\json-schema"), "com.finos.fdc3.java.api", + new File("C:\\finos\\json-schema"), "org.finos.fdc3.java.api", "Sample"); }catch(Throwable t) { t.printStackTrace(); diff --git a/client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java b/unused/client/src/main/java/org/finos/fdc3/client/utils/SampleDataLoaderUtil.java similarity index 94% rename from client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java rename to unused/client/src/main/java/org/finos/fdc3/client/utils/SampleDataLoaderUtil.java index 95df7503..92e1e57c 100644 --- a/client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/utils/SampleDataLoaderUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.client.utils; +package org.finos.fdc3.client.utils; import java.io.BufferedReader; import java.io.IOException; @@ -27,9 +27,9 @@ import java.util.Collections; import java.util.List; -import com.finos.fdc3.api.utils.JacksonUtilities; -import com.finos.fdc3.client.data.OrderData; -import com.finos.fdc3.client.data.SnPData; +import org.finos.fdc3.api.utils.JacksonUtilities; +import org.finos.fdc3.client.data.OrderData; +import org.finos.fdc3.client.data.SnPData; public class SampleDataLoaderUtil { diff --git a/client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java b/unused/client/src/main/java/org/finos/fdc3/client/utils/StringUtilities.java similarity index 91% rename from client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java rename to unused/client/src/main/java/org/finos/fdc3/client/utils/StringUtilities.java index 122c4d91..a81eded0 100644 --- a/client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/utils/StringUtilities.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.client.utils; +package org.finos.fdc3.client.utils; -import com.finos.fdc3.api.utils.JacksonUtilities; +import org.finos.fdc3.api.utils.JacksonUtilities; public class StringUtilities { diff --git a/client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java b/unused/client/src/main/java/org/finos/fdc3/client/utils/SwingUtils.java similarity index 93% rename from client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java rename to unused/client/src/main/java/org/finos/fdc3/client/utils/SwingUtils.java index 44b3a276..6f85b087 100644 --- a/client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java +++ b/unused/client/src/main/java/org/finos/fdc3/client/utils/SwingUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.client.utils; +package org.finos.fdc3.client.utils; -import com.finos.fdc3.client.AppGUIException; +import org.finos.fdc3.client.AppGUIException; import javax.swing.*; import java.awt.*; diff --git a/fdc3container/fdc3-platform.json b/unused/fdc3container/fdc3-platform.json similarity index 100% rename from fdc3container/fdc3-platform.json rename to unused/fdc3container/fdc3-platform.json diff --git a/fdc3container/package.json b/unused/fdc3container/package.json similarity index 100% rename from fdc3container/package.json rename to unused/fdc3container/package.json diff --git a/fdc3container/receiver.html b/unused/fdc3container/receiver.html similarity index 100% rename from fdc3container/receiver.html rename to unused/fdc3container/receiver.html diff --git a/fdc3container/receiver.json b/unused/fdc3container/receiver.json similarity index 100% rename from fdc3container/receiver.json rename to unused/fdc3container/receiver.json diff --git a/fdc3container/receiver/receiver.js b/unused/fdc3container/receiver/receiver.js similarity index 100% rename from fdc3container/receiver/receiver.js rename to unused/fdc3container/receiver/receiver.js diff --git a/fdc3container/receiver/style.css b/unused/fdc3container/receiver/style.css similarity index 100% rename from fdc3container/receiver/style.css rename to unused/fdc3container/receiver/style.css diff --git a/fdc3container/sender.html b/unused/fdc3container/sender.html similarity index 100% rename from fdc3container/sender.html rename to unused/fdc3container/sender.html diff --git a/fdc3container/sender.json b/unused/fdc3container/sender.json similarity index 100% rename from fdc3container/sender.json rename to unused/fdc3container/sender.json diff --git a/fdc3container/server.js b/unused/fdc3container/server.js similarity index 100% rename from fdc3container/server.js rename to unused/fdc3container/server.js diff --git a/fdcreceiver-react-trade-app/package-lock.json b/unused/fdcreceiver-react-trade-app/package-lock.json similarity index 100% rename from fdcreceiver-react-trade-app/package-lock.json rename to unused/fdcreceiver-react-trade-app/package-lock.json diff --git a/fdcreceiver-react-trade-app/package.json b/unused/fdcreceiver-react-trade-app/package.json similarity index 100% rename from fdcreceiver-react-trade-app/package.json rename to unused/fdcreceiver-react-trade-app/package.json diff --git a/fdcreceiver-react-trade-app/public/favicon.ico b/unused/fdcreceiver-react-trade-app/public/favicon.ico similarity index 100% rename from fdcreceiver-react-trade-app/public/favicon.ico rename to unused/fdcreceiver-react-trade-app/public/favicon.ico diff --git a/fdcreceiver-react-trade-app/public/index.html b/unused/fdcreceiver-react-trade-app/public/index.html similarity index 100% rename from fdcreceiver-react-trade-app/public/index.html rename to unused/fdcreceiver-react-trade-app/public/index.html diff --git a/fdcreceiver-react-trade-app/public/logo192.png b/unused/fdcreceiver-react-trade-app/public/logo192.png similarity index 100% rename from fdcreceiver-react-trade-app/public/logo192.png rename to unused/fdcreceiver-react-trade-app/public/logo192.png diff --git a/fdcreceiver-react-trade-app/public/logo512.png b/unused/fdcreceiver-react-trade-app/public/logo512.png similarity index 100% rename from fdcreceiver-react-trade-app/public/logo512.png rename to unused/fdcreceiver-react-trade-app/public/logo512.png diff --git a/fdcreceiver-react-trade-app/public/manifest.json b/unused/fdcreceiver-react-trade-app/public/manifest.json similarity index 100% rename from fdcreceiver-react-trade-app/public/manifest.json rename to unused/fdcreceiver-react-trade-app/public/manifest.json diff --git a/fdcreceiver-react-trade-app/public/robots.txt b/unused/fdcreceiver-react-trade-app/public/robots.txt similarity index 100% rename from fdcreceiver-react-trade-app/public/robots.txt rename to unused/fdcreceiver-react-trade-app/public/robots.txt diff --git a/fdcreceiver-react-trade-app/src/App.js b/unused/fdcreceiver-react-trade-app/src/App.js similarity index 100% rename from fdcreceiver-react-trade-app/src/App.js rename to unused/fdcreceiver-react-trade-app/src/App.js diff --git a/fdcreceiver-react-trade-app/src/components/Card.js b/unused/fdcreceiver-react-trade-app/src/components/Card.js similarity index 100% rename from fdcreceiver-react-trade-app/src/components/Card.js rename to unused/fdcreceiver-react-trade-app/src/components/Card.js diff --git a/fdcreceiver-react-trade-app/src/components/ChannelDropdown.js b/unused/fdcreceiver-react-trade-app/src/components/ChannelDropdown.js similarity index 100% rename from fdcreceiver-react-trade-app/src/components/ChannelDropdown.js rename to unused/fdcreceiver-react-trade-app/src/components/ChannelDropdown.js diff --git a/fdcreceiver-react-trade-app/src/components/Scroll.js b/unused/fdcreceiver-react-trade-app/src/components/Scroll.js similarity index 100% rename from fdcreceiver-react-trade-app/src/components/Scroll.js rename to unused/fdcreceiver-react-trade-app/src/components/Scroll.js diff --git a/fdcreceiver-react-trade-app/src/components/Search.js b/unused/fdcreceiver-react-trade-app/src/components/Search.js similarity index 100% rename from fdcreceiver-react-trade-app/src/components/Search.js rename to unused/fdcreceiver-react-trade-app/src/components/Search.js diff --git a/fdcreceiver-react-trade-app/src/components/SearchList.js b/unused/fdcreceiver-react-trade-app/src/components/SearchList.js similarity index 100% rename from fdcreceiver-react-trade-app/src/components/SearchList.js rename to unused/fdcreceiver-react-trade-app/src/components/SearchList.js diff --git a/fdcreceiver-react-trade-app/src/data/initialDetails.js b/unused/fdcreceiver-react-trade-app/src/data/initialDetails.js similarity index 100% rename from fdcreceiver-react-trade-app/src/data/initialDetails.js rename to unused/fdcreceiver-react-trade-app/src/data/initialDetails.js diff --git a/fdcreceiver-react-trade-app/src/index.js b/unused/fdcreceiver-react-trade-app/src/index.js similarity index 100% rename from fdcreceiver-react-trade-app/src/index.js rename to unused/fdcreceiver-react-trade-app/src/index.js diff --git a/openfin-fdc3-adapter/pom.xml b/unused/openfin-fdc3-adapter/pom.xml similarity index 94% rename from openfin-fdc3-adapter/pom.xml rename to unused/openfin-fdc3-adapter/pom.xml index c7672069..b4258aa6 100644 --- a/openfin-fdc3-adapter/pom.xml +++ b/unused/openfin-fdc3-adapter/pom.xml @@ -21,7 +21,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.finos.fdc3.api + org.finos.fdc3.api finos-bmo-hackathon 1.0.0-SNAPSHOT @@ -41,7 +41,7 @@ 11.0.1 - com.finos.fdc3.api + org.finos.fdc3.api fdc3api ${project.version} compile diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannel.java similarity index 89% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannel.java index 0cb1f246..fdfa2cfd 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannel.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.finos.fdc3.adapter.openfin; +package org.finos.fdc3.adapter.openfin; import java.util.Optional; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.DisplayMetadata; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.DisplayMetadata; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.Listener; import com.openfin.desktop.interop.ContextGroupInfo; /** diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java similarity index 89% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java index a4baa5ac..d311cac7 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.adapter.openfin; +package org.finos.fdc3.adapter.openfin; import java.util.Optional; @@ -22,11 +22,11 @@ /** * Adapter to convert {@link com.openfin.desktop.interop.DisplayMetadata} to - * {@link com.finos.fdc3.api.common.metadata.DisplayMetadata} + * {@link org.finos.fdc3.api.common.metadata.DisplayMetadata} * * @author Tim Jenkel */ -public class OpenFinChannelDisplayMetadata implements com.finos.fdc3.api.metadata.DisplayMetadata { +public class OpenFinChannelDisplayMetadata implements org.finos.fdc3.api.metadata.DisplayMetadata { private DisplayMetadata displayMetadata; diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java similarity index 89% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java index 4d2e59c4..007a5871 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.adapter.openfin; +package org.finos.fdc3.adapter.openfin; import java.util.Objects; -import com.finos.fdc3.api.context.Position; +import org.finos.fdc3.api.context.Position; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.utils.JacksonUtilities; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.utils.JacksonUtilities; import com.openfin.desktop.interop.ContextListener; /** diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java similarity index 93% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java index 36f774c1..9ce657bf 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.adapter.openfin; +package org.finos.fdc3.adapter.openfin; import java.util.List; import java.util.Optional; @@ -28,20 +28,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.api.DesktopAgent; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.Channel.Type; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.errors.FDC3ConnectionException; -import com.finos.fdc3.api.metadata.AppIntent; -import com.finos.fdc3.api.metadata.AppMetadata; -import com.finos.fdc3.api.metadata.ImplementationMetadata; -import com.finos.fdc3.api.metadata.IntentResolution; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.DesktopAgent; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.Channel.Type; +import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.errors.FDC3ConnectionException; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.Listener; import com.openfin.desktop.DesktopConnection; import com.openfin.desktop.DesktopStateListener; import com.openfin.desktop.interop.Intent; diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java similarity index 97% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java index ddf528c0..1584bd44 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.adapter.openfin; +package org.finos.fdc3.adapter.openfin; import java.time.Duration; diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java similarity index 89% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java rename to unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java index 5e4e884a..524e0ae2 100644 --- a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java +++ b/unused/openfin-fdc3-adapter/src/main/java/org/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ - package com.finos.fdc3.adapter.openfin; + package org.finos.fdc3.adapter.openfin; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -22,10 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.IntentResult; -import com.finos.fdc3.api.utils.JacksonUtilities; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.types.IntentHandler; +import org.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.api.utils.JacksonUtilities; import com.openfin.desktop.interop.Intent; import com.openfin.desktop.interop.IntentListener;