From 288c5151017de0b98862509a33e53f5e1f4e2fa3 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 10 Dec 2025 09:23:14 +0000 Subject: [PATCH 01/65] Added 2.2 event handling, fixed typos --- .gitignore | 3 + .../java/com/finos/fdc3/api/DesktopAgent.java | 1199 ++++++++++------- .../finos/fdc3/api/types/EventHandler.java | 34 + .../com/finos/fdc3/api/types/FDC3Event.java | 56 + 4 files changed, 792 insertions(+), 500 deletions(-) create mode 100644 fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java create mode 100644 fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java diff --git a/.gitignore b/.gitignore index fa116c91..a73cfdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ docs/contributing.md # We use YARN website/package-lock.json + +# Maven target +target/ diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java b/fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java index aed70ef2..5330b04f 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java @@ -25,6 +25,7 @@ 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.EventHandler; import com.finos.fdc3.api.types.IntentHandler; import com.finos.fdc3.api.types.Listener; @@ -33,525 +34,723 @@ import java.util.concurrent.CompletionStage; /** - * A Desktop Agent is a desktop component (or aggregate of components) that serves as a + * A Desktop Agent is a desktop component (or aggregate of components) that + * serves as a * launcher and message router (broker) for applications in its domain. * - * A Desktop Agent can be connected to one or more App Directories and will use directories for application - * identity and discovery. Typically, a Desktop Agent will contain the proprietary logic of - * a given platform, handling functionality like explicit application interop workflows where + * A Desktop Agent can be connected to one or more App Directories and will use + * directories for application + * identity and discovery. Typically, a Desktop Agent will contain the + * proprietary logic of + * a given platform, handling functionality like explicit application interop + * workflows where * security, consistency, and implementation requirements are proprietary. */ public interface DesktopAgent { - /** - * Launches an app, specified via an `AppIdentifier` object. - * - * The `open` method differs in use from `raiseIntent`. Generally, it should be used when the target application is known but there is no specific intent. For example, if an application is querying the App Directory, `open` would be used to open an app returned in the search results. - * - * **Note**, if the intent, context and target app name are all known, it is recommended to instead use `raiseIntent` with the `target` argument. - * - * 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. - * - * Returns an `AppIdentifier` object with the `instanceId` field set identifying the instance of the application opened by this call. - * - * If an error occurs while opening the app, the promise MUST be rejected with an `Error` Object with a `message` chosen from the `OpenError` enumeration. - * - * ```javascript - * //Open an app without context, using an AppIdentifier object to specify the target by `appId`. - * let appIdentifier = {appId: 'myApp-v1.0.1'}; - * let instanceIdentifier = await fdc3.open(appIdentifier); - * - * //Open an app with context - * let instanceIdentifier = await fdc3.open(appIdentifier, context); - * ``` - */ - CompletionStage open(AppIdentifier app, Context context); - - default CompletionStage open(AppIdentifier app) { - return open(app, null); - } + /** + * Launches an app, specified via an `AppIdentifier` object. + * + * The `open` method differs in use from `raiseIntent`. Generally, it should be + * used when the target application is known but there is no specific intent. + * For example, if an application is querying the App Directory, `open` would be + * used to open an app returned in the search results. + * + * **Note**, if the intent, context and target app name are all known, it is + * recommended to instead use `raiseIntent` with the `target` argument. + * + * 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. + * + * Returns an `AppIdentifier` object with the `instanceId` field set identifying + * the instance of the application opened by this call. + * + * If an error occurs while opening the app, the promise MUST be rejected with + * an `Error` Object with a `message` chosen from the `OpenError` enumeration. + * + * ```javascript + * //Open an app without context, using an AppIdentifier object to specify the + * target by `appId`. + * let appIdentifier = {appId: 'myApp-v1.0.1'}; + * let instanceIdentifier = await fdc3.open(appIdentifier); + * + * //Open an app with context + * let instanceIdentifier = await fdc3.open(appIdentifier, context); + * ``` + */ + CompletionStage open(AppIdentifier app, Context context); - /** - * Find out more information about a particular intent by passing its name, and optionally its context and/or a desired result context type. - * - * `findIntent` is effectively granting programmatic access to the Desktop Agent's resolver. - * It returns a promise resolving to the intent, its metadata and metadata about the apps and app instances that registered that intent. - * This can be used to raise the intent against a specific app or app instance. - * - * If the resolution fails, the promise MUST be rejected with an `Error` Object with a `message` chosen from the `ResolveError` enumeration. This includes the case where no apps are found that resolve the intent, when the `ResolveError.NoAppsFound` message should be used. - * - * Result types may be a type name, the string "channel" (which indicates that the app - * will return a channel) or a string indicating a channel that returns a specific type, - * e.g. "channel". - * - * If intent resolution to an app returning a channel is requested, the desktop agent - * MUST include both apps that are registered as returning a channel and those registered - * as returning a channel with a specific type in the response. - * - * ```javascript - * // I know 'StartChat' exists as a concept, and want to know which apps can resolve it ... - * const appIntent = await fdc3.findIntent("StartChat"); - * - * // returns a single AppIntent: - * // { - * // intent: { name: "StartChat", displayName: "Chat" }, - * // apps: [ - * // { appId: "Skype" }, - * // { appId: "Symphony" }, - * // { appId: "Slack" } - * // ] - * // } - * - * // raise the intent against a particular app - * await fdc3.raiseIntent(appIntent.intent.name, context, appIntent.apps[0]); - * - * //later, we want to raise 'StartChat' intent again - * const appIntent = await fdc3.findIntent("StartChat"); - * // returns an AppIntent, but with multiple options for resolution, - * // which includes an existing instance of an application: - * // { - * // intent: { name: "StartChat", displayName: "Chat" }, - * // apps: [ - * // { appId: "Skype" }, - * // { appId: "Symphony" }, - * // { appId: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" }, - * // { appId: "Slack" } - * // ] - * ``` - * - * An optional input context object and result type may be specified, which the resolver MUST use to filter the returned applications such that each supports the specified input and result types. - * - * ```javascript - * const appIntent = await fdc3.findIntent("StartChat", contact); - * - * // returns only apps that support the type of the specified input context: - * // { - * // intent: { name: "StartChat", displayName: "Chat" }, - * // apps: [{ appId: "Symphony" }] - * // } - * - * const appIntent = await fdc3.findIntent("ViewContact", contact, "fdc3.ContactList"); - * - * // returns only apps that return the specified result Context type: - * // { - * // intent: { name: "ViewContact", displayName: "View Contact Details" }, - * // apps: { appId: "MyCRM", resultType: "fdc3.ContactList"}] - * // } - * - * const appIntent = await fdc3.findIntent("QuoteStream", instrument, "channel"); - * - * // returns only apps that return a channel which will receive the specified input and result types: - * // { - * // intent: { name: "QuoteStream", displayName: "Quotes stream" }, - * // apps: [{ appId: "MyOMS", resultType: "channel"}] - * // } - * ``` - */ - CompletionStage findIntent(String indent, Context context, String resultType); - - default CompletionStage findIntent(String indent, Context context) { - return findIntent(indent, context, null); - } - - default CompletionStage findIntent(String indent) { - return findIntent(indent, null); - } + default CompletionStage open(AppIdentifier app) { + return open(app, null); + } - /** - * Find all the available intents for a particular context, and optionally a desired result context type. - * - * `findIntentsByContext` is effectively granting programmatic access to the Desktop Agent's resolver. - * It returns a promise resolving to an `AppIntent` which provides details of the intent, its metadata and metadata about the apps and app instances that are registered to handle it. This can be used to raise the intent against a specific app or app instance. - * - * If the resolution fails, the promise MUST be rejected with an `Error` Object with a `message` chosen from the `ResolveError` enumeration. This includes the case where no intents with associated apps are found, when the `ResolveError.NoAppsFound` message should be used. - * - * The optional `resultType` argument may be a type name, the string "channel" (which indicates that the app - * should return a channel) or a string indicating a channel that returns a specific type, - * e.g. "channel". If intent resolution to an app returning a channel is requested without - * a specified context type, the desktop agent MUST also include apps that are registered as returning a - * channel with a specific type in the response. - * - * ```javascript - * // I have a context object, and I want to know what I can do with it, hence, I look for intents and apps to resolve them... - * const appIntents = await fdc3.findIntentsByContext(context); - * - * // returns for example: - * // [ - * // { - * // intent: { name: "StartCall", displayName: "Call" }, - * // apps: [{ name: "Skype" }] - * // }, - * // { - * // intent: { name: "StartChat", displayName: "Chat" }, - * // apps: [ - * // { appId: "Skype" }, - * // { appId: "Symphony" }, - * // { appId: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" }, - * // { appId: "Slack" } - * // ] - * // } - * // ]; - * - * // or I look for only intents that are resolved by apps returning a particular result type - * const appIntentsForType = await fdc3.findIntentsByContext(context, "fdc3.ContactList"); - * // returns for example: - * // [{ - * // intent: { name: "ViewContact", displayName: "View Contacts" }, - * // apps: [{ appId: "MyCRM", resultType: "fdc3.ContactList"}] - * // }]; - * - * // select a particular intent to raise - * const resolvedIntent = appIntents[1]; - * - * // target a particular app or instance - * const selectedApp = resolvedIntent.apps[2]; - * - * // raise the intent, passing the given context, targeting the app or app instance - * await fdc3.raiseIntent(startChat.intent.name, context, selectedApp); - * ``` - */ - CompletionStage> findIntentsByContext(Context context, String resultType); - - default CompletionStage> findIntentsByContext(Context context) { - return findIntentsByContext(context, null); - } + /** + * Find out more information about a particular intent by passing its name, and + * optionally its context and/or a desired result context type. + * + * `findIntent` is effectively granting programmatic access to the Desktop + * Agent's resolver. + * It returns a promise resolving to the intent, its metadata and metadata about + * the apps and app instances that registered that intent. + * This can be used to raise the intent against a specific app or app instance. + * + * If the resolution fails, the promise MUST be rejected with an `Error` Object + * with a `message` chosen from the `ResolveError` enumeration. This includes + * the case where no apps are found that resolve the intent, when the + * `ResolveError.NoAppsFound` message should be used. + * + * Result types may be a type name, the string "channel" (which indicates that + * the app + * will return a channel) or a string indicating a channel that returns a + * specific type, + * e.g. "channel". + * + * If intent resolution to an app returning a channel is requested, the desktop + * agent + * MUST include both apps that are registered as returning a channel and those + * registered + * as returning a channel with a specific type in the response. + * + * ```javascript + * // I know 'StartChat' exists as a concept, and want to know which apps can + * resolve it ... + * const appIntent = await fdc3.findIntent("StartChat"); + * + * // returns a single AppIntent: + * // { + * // intent: { name: "StartChat", displayName: "Chat" }, + * // apps: [ + * // { appId: "Skype" }, + * // { appId: "Symphony" }, + * // { appId: "Slack" } + * // ] + * // } + * + * // raise the intent against a particular app + * await fdc3.raiseIntent(appIntent.intent.name, context, appIntent.apps[0]); + * + * //later, we want to raise 'StartChat' intent again + * const appIntent = await fdc3.findIntent("StartChat"); + * // returns an AppIntent, but with multiple options for resolution, + * // which includes an existing instance of an application: + * // { + * // intent: { name: "StartChat", displayName: "Chat" }, + * // apps: [ + * // { appId: "Skype" }, + * // { appId: "Symphony" }, + * // { appId: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" }, + * // { appId: "Slack" } + * // ] + * ``` + * + * An optional input context object and result type may be specified, which the + * resolver MUST use to filter the returned applications such that each supports + * the specified input and result types. + * + * ```javascript + * const appIntent = await fdc3.findIntent("StartChat", contact); + * + * // returns only apps that support the type of the specified input context: + * // { + * // intent: { name: "StartChat", displayName: "Chat" }, + * // apps: [{ appId: "Symphony" }] + * // } + * + * const appIntent = await fdc3.findIntent("ViewContact", contact, + * "fdc3.ContactList"); + * + * // returns only apps that return the specified result Context type: + * // { + * // intent: { name: "ViewContact", displayName: "View Contact Details" }, + * // apps: { appId: "MyCRM", resultType: "fdc3.ContactList"}] + * // } + * + * const appIntent = await fdc3.findIntent("QuoteStream", instrument, + * "channel"); + * + * // returns only apps that return a channel which will receive the specified + * input and result types: + * // { + * // intent: { name: "QuoteStream", displayName: "Quotes stream" }, + * // apps: [{ appId: "MyOMS", resultType: "channel"}] + * // } + * ``` + */ + CompletionStage findIntent(String intent, Context context, String resultType); - /** - * Find all the available instances for a particular application. - * - * If there are no instances of the specified application the returned promise should resolve to an empty array. - * - * If the request fails for another reason, the promise MUST be rejected with an `Error` Object with a `message` chosen from the `ResolveError` enumeration. - * - * ```javascript - * // Retrieve a list of instances of an application - * let instances = await fdc3.findInstances({appId: "MyAppId"}); - * - * // Target a raised intent at a specific instance - * let resolution = fdc3.raiseIntent("ViewInstrument", context, instances[0]); - * ``` - * @param app - */ - CompletionStage> findInstances(AppIdentifier app); + default CompletionStage findIntent(String intent, Context context) { + return findIntent(intent, context, null); + } - /** - * Publishes context to other apps on the desktop. Calling `broadcast` at the `DesktopAgent` scope will push the context to whatever _User Channel_ the app is joined to. If the app is not currently joined to a channel, calling `fdc3.broadcast` will have no effect. Apps can still directly broadcast and listen to context on any channel via the methods on the `Channel` class. - * - * DesktopAgent implementations should ensure that context messages broadcast to a channel by an application joined to it should not be delivered back to that same application. - * - * If you are working with complex context types composed of other simpler types then you should broadcast - * each individual type (starting with the simpler types, followed by the complex type) that you want other - * apps to be able to respond to. Doing so allows applications to filter the context types they receive by - * adding listeners for specific context types. - * - * ```javascript - * const instrument = { - * type: 'fdc3.instrument', - * id: { - * ticker: 'AAPL' - * } - * }; - * fdc3.broadcast(context); - * ``` - */ - CompletionStage broadcast(Context context); + default CompletionStage findIntent(String intent) { + return findIntent(intent, null); + } - /** - * Raises a specific intent for resolution against apps registered with the desktop agent. - * - * The desktop agent MUST resolve the correct app to target based on the provided intent name and context data. If multiple matching apps are found, the user MAY be presented with a Resolver UI allowing them to pick one, or another method of Resolution applied to select an app. - * Alternatively, the specific app or app instance to target can also be provided. A list of valid target applications and instances can be retrieved via `findIntent`. - * - * If a target app for the intent cannot be found with the criteria provided or the user either closes the resolver UI or otherwise cancels resolution, the promise MUST be rejected with an `Error` object with a `message` chosen from the `ResolveError` enumeration. If a specific target `app` parameter was set, but either the app or app instance is not available, the promise MUST be rejected with an `Error` object with either the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` string as its `message`. - * - * If you wish to raise an Intent without a context, use the `fdc3.nothing` context type. This type exists so that apps can explicitly declare support for raising an intent without context. - * - * Returns an `IntentResolution` object with details of the app instance that was selected (or started) to respond to the intent. - * - * Issuing apps may optionally wait on the promise that is returned by the `getResult()` member of the `IntentResolution`. This promise will resolve when the _receiving app's_ intent handler function returns and resolves a promise. The Desktop Agent resolves the issuing app's promise with the Context object or Channel that is provided as resolution within the receiving app. The Desktop Agent MUST reject the issuing app's promise, with a string from the `ResultError` enumeration, if: (1) the intent handling function's returned promise rejects, (2) the intent handling function doesn't return a promise, or (3) the returned promise resolves to an invalid type. - * - * ```javascript - * // raise an intent for resolution by the desktop agent - * // a resolver UI may be displayed if more than one application can resolve the intent - * await fdc3.raiseIntent("StartChat", context); - * - * // or find apps to resolve an intent to start a chat with a given contact - * const appIntent = await fdc3.findIntent("StartChat", context); - * // use the metadata of an app or app instance to describe the target app for the intent - * await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]); - * - * //Raise an intent without a context by using the null context type - * await fdc3.raiseIntent("StartChat", {type: "fdc3.nothing"}); - * - * //Raise an intent and retrieve a result from the IntentResolution - * let resolution = await agent.raiseIntent("intentName", context); - * try { - * const result = await resolution.getResult(); - * if (result && result.broadcast) { //detect whether the result is Context or a Channel - * console.log(`${resolution.source} returned a channel with id ${result.id}`); - * } else if (result){ - * console.log(`${resolution.source} returned data: ${JSON.stringify(result)}`); - * } else { - * console.error(`${resolution.source} didn't return anything` - * } - * } catch(error) { - * console.error(`${resolution.source} returned an error: ${error}`); - * } - * ``` - */ - CompletionStage raiseIntent(String intent, Context context, AppIdentifier app); - - default CompletionStage raiseIntent(String intent, Context context) { - return raiseIntent(intent, context, null); - } + /** + * Find all the available intents for a particular context, and optionally a + * desired result context type. + * + * `findIntentsByContext` is effectively granting programmatic access to the + * Desktop Agent's resolver. + * It returns a promise resolving to an `AppIntent` which provides details of + * the intent, its metadata and metadata about the apps and app instances that + * are registered to handle it. This can be used to raise the intent against a + * specific app or app instance. + * + * If the resolution fails, the promise MUST be rejected with an `Error` Object + * with a `message` chosen from the `ResolveError` enumeration. This includes + * the case where no intents with associated apps are found, when the + * `ResolveError.NoAppsFound` message should be used. + * + * The optional `resultType` argument may be a type name, the string "channel" + * (which indicates that the app + * should return a channel) or a string indicating a channel that returns a + * specific type, + * e.g. "channel". If intent resolution to an app returning a + * channel is requested without + * a specified context type, the desktop agent MUST also include apps that are + * registered as returning a + * channel with a specific type in the response. + * + * ```javascript + * // I have a context object, and I want to know what I can do with it, hence, + * I look for intents and apps to resolve them... + * const appIntents = await fdc3.findIntentsByContext(context); + * + * // returns for example: + * // [ + * // { + * // intent: { name: "StartCall", displayName: "Call" }, + * // apps: [{ name: "Skype" }] + * // }, + * // { + * // intent: { name: "StartChat", displayName: "Chat" }, + * // apps: [ + * // { appId: "Skype" }, + * // { appId: "Symphony" }, + * // { appId: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" }, + * // { appId: "Slack" } + * // ] + * // } + * // ]; + * + * // or I look for only intents that are resolved by apps returning a + * particular result type + * const appIntentsForType = await fdc3.findIntentsByContext(context, + * "fdc3.ContactList"); + * // returns for example: + * // [{ + * // intent: { name: "ViewContact", displayName: "View Contacts" }, + * // apps: [{ appId: "MyCRM", resultType: "fdc3.ContactList"}] + * // }]; + * + * // select a particular intent to raise + * const resolvedIntent = appIntents[1]; + * + * // target a particular app or instance + * const selectedApp = resolvedIntent.apps[2]; + * + * // raise the intent, passing the given context, targeting the app or app + * instance + * await fdc3.raiseIntent(startChat.intent.name, context, selectedApp); + * ``` + */ + CompletionStage> findIntentsByContext(Context context, String resultType); - /** - * Finds and raises an intent against apps registered with the desktop agent based on the type of the specified context data example. - * - * The desktop agent SHOULD first resolve to a specific intent based on the provided context if more than one intent is available for the specified context. This MAY be achieved by displaying a resolver UI. It SHOULD then resolve to a specific app to handle the selected intent and specified context. - * Alternatively, the specific app or app instance to target can also be provided, in which case the resolver SHOULD only offer intents supported by the specified application. - * - * Using `raiseIntentForContext` is similar to calling `findIntentsByContext`, and then raising an intent against one of the returned apps, except in this case the desktop agent has the opportunity to provide the user with a richer selection interface where they can choose both the intent and target app. - * - * Returns an `IntentResolution` object, see `raiseIntent()` for details. - * - * If a target intent and app cannot be found with the criteria provided or the user either closes the resolver UI or otherwise cancels resolution, the promise MUST be rejected with an `Error` object with a `message` chosen from the `ResolveError` enumeration. If a specific target `app` parameter was set, but either the app or app instance is not available, the promise MUST be rejected with an `Error` object with either the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` string as its `message`. - * - * ```javascript - * // Resolve against all intents registered for the type of the specified context - * await fdc3.raiseIntentForContext(context); - * - * // Resolve against all intents registered by a specific target app for the specified context - * await fdc3.raiseIntentForContext(context, targetAppIdentifier); - * ``` - */ - CompletionStage raiseIntentForContext(Context context, AppIdentifier app); - - default CompletionStage raiseIntentForContext(Context context) { - return raiseIntentForContext(context, null); - } + default CompletionStage> findIntentsByContext(Context context) { + return findIntentsByContext(context, null); + } - /** - * Adds a listener for incoming Intents from the Agent. The handler function may return void or a promise that should resolve to an `IntentResult`, which is either a `Context` object, representing any data that should be returned, or a `Channel` over which data responses will be sent. The IntentResult will be returned to app that raised the intent via the `IntentResolution` and retrieved from it using the `getResult()` function. - * - * The Desktop Agent MUST reject the promise returned by the `getResult()` function of `IntentResolution` if: (1) the intent handling function's returned promise rejects, (2) the intent handling function doesn't return a promise, or (3) the returned promise resolves to an invalid type. - * - * The `PrivateChannel` type is provided to support synchronisation of data transmitted over returned channels, by allowing both parties to listen for events denoting subscription and unsubscription from the returned channel. `PrivateChannels` are only retrievable via raising an intent. - * - * Optional metadata about the raised intent, including the app that originated the message, SHOULD be provided by the desktop agent implementation. - * - * ```javascript - * //Handle a raised intent - * const listener = fdc3.addIntentListener('StartChat', context => { - * // start chat has been requested by another application - * return; - * }); - * - * //Handle a raised intent and log the originating app metadata - * const listener = fdc3.addIntentListener('StartChat', (contact, metadata) => { - * console.log(`Received intent StartChat\nContext: ${contact}\nOriginating app: ${metadata?.source}`); - * return; - * }); - * - * //Handle a raised intent and return Context data via a promise - * fdc3.addIntentListener("CreateOrder", (context) => { - * return new Promise((resolve) => { - * // go create the order - * resolve({type: "fdc3.order", id: { "orderId": 1234}}); - * }); - * }); - * - * //Handle a raised intent and return a Private Channel over which response will be sent - * fdc3.addIntentListener("QuoteStream", async (context) => { - * const channel: PrivateChannel = await fdc3.createPrivateChannel(); - * const symbol = context.id.symbol; - * - * // Called when the remote side adds a context listener - * const addContextListener = channel.onAddContextListener((contextType) => { - * // broadcast price quotes as they come in from our quote feed - * feed.onQuote(symbol, (price) => { - * channel.broadcast({ type: "price", price}); - * }); - * }); - * - * // Stop the feed if the remote side closes - * const disconnectListener = channel.onDisconnect(() => { - * feed.stop(symbol); - * }); - * - * return channel; - * }); - * ``` - */ - CompletionStage addIntentListener(String intent, IntentHandler handler); + /** + * Find all the available instances for a particular application. + * + * If there are no instances of the specified application the returned promise + * should resolve to an empty array. + * + * If the request fails for another reason, the promise MUST be rejected with an + * `Error` Object with a `message` chosen from the `ResolveError` enumeration. + * + * ```javascript + * // Retrieve a list of instances of an application + * let instances = await fdc3.findInstances({appId: "MyAppId"}); + * + * // Target a raised intent at a specific instance + * let resolution = fdc3.raiseIntent("ViewInstrument", context, instances[0]); + * ``` + * + * @param app + */ + CompletionStage> findInstances(AppIdentifier app); - /** - * Adds a listener for incoming context broadcasts from the Desktop Agent via User channels. If the consumer is only interested in a context of a particular type, they can they can specify that type. If the consumer is able to receive context of any type or will inspect types received, then they can pass `null` as the `contextType` parameter to receive all context types. - * - * Context broadcasts are only received from apps that are joined to the same User channel as the listening application, hence, if the application is not currently joined to a channel no broadcasts will be received. If this function is called after the app has already joined a channel and the channel already contains context that would be passed to the context listener, then it will be called immediately with that context. - * - * Optional metadata about the context message, including the app that originated the message, SHOULD be provided by the desktop agent implementation. - * - * ```javascript - * // any context - * const listener = await fdc3.addContextListener(null, context => { ... }); - * - * // listener for a specific type - * const contactListener = await fdc3.addContextListener('fdc3.contact', contact => { ... }); - * - * // listener that logs metadata for the message a specific type - * const contactListener = await fdc3.addContextListener('fdc3.contact', (contact, metadata) => { - * console.log(`Received context message\nContext: ${contact}\nOriginating app: ${metadata?.source}`); - * //do something else with the context - * }); - * ``` - */ - CompletionStage addContextListener(String contextType, ContextHandler handler); + /** + * Publishes context to other apps on the desktop. Calling `broadcast` at the + * `DesktopAgent` scope will push the context to whatever _User Channel_ the app + * is joined to. If the app is not currently joined to a channel, calling + * `fdc3.broadcast` will have no effect. Apps can still directly broadcast and + * listen to context on any channel via the methods on the `Channel` class. + * + * DesktopAgent implementations should ensure that context messages broadcast to + * a channel by an application joined to it should not be delivered back to that + * same application. + * + * If you are working with complex context types composed of other simpler types + * then you should broadcast + * each individual type (starting with the simpler types, followed by the + * complex type) that you want other + * apps to be able to respond to. Doing so allows applications to filter the + * context types they receive by + * adding listeners for specific context types. + * + * ```javascript + * const instrument = { + * type: 'fdc3.instrument', + * id: { + * ticker: 'AAPL' + * } + * }; + * fdc3.broadcast(context); + * ``` + */ + CompletionStage broadcast(Context context); - /** - * Retrieves a list of the User channels available for the app to join. - */ - CompletionStage> getUserChannels(); + /** + * Raises a specific intent for resolution against apps registered with the + * desktop agent. + * + * The desktop agent MUST resolve the correct app to target based on the + * provided intent name and context data. If multiple matching apps are found, + * the user MAY be presented with a Resolver UI allowing them to pick one, or + * another method of Resolution applied to select an app. + * Alternatively, the specific app or app instance to target can also be + * provided. A list of valid target applications and instances can be retrieved + * via `findIntent`. + * + * If a target app for the intent cannot be found with the criteria provided or + * the user either closes the resolver UI or otherwise cancels resolution, the + * promise MUST be rejected with an `Error` object with a `message` chosen from + * the `ResolveError` enumeration. If a specific target `app` parameter was set, + * but either the app or app instance is not available, the promise MUST be + * rejected with an `Error` object with either the + * `ResolveError.TargetAppUnavailable` or + * `ResolveError.TargetInstanceUnavailable` string as its `message`. + * + * If you wish to raise an Intent without a context, use the `fdc3.nothing` + * context type. This type exists so that apps can explicitly declare support + * for raising an intent without context. + * + * Returns an `IntentResolution` object with details of the app instance that + * was selected (or started) to respond to the intent. + * + * Issuing apps may optionally wait on the promise that is returned by the + * `getResult()` member of the `IntentResolution`. This promise will resolve + * when the _receiving app's_ intent handler function returns and resolves a + * promise. The Desktop Agent resolves the issuing app's promise with the + * Context object or Channel that is provided as resolution within the receiving + * app. The Desktop Agent MUST reject the issuing app's promise, with a string + * from the `ResultError` enumeration, if: (1) the intent handling function's + * returned promise rejects, (2) the intent handling function doesn't return a + * promise, or (3) the returned promise resolves to an invalid type. + * + * ```javascript + * // raise an intent for resolution by the desktop agent + * // a resolver UI may be displayed if more than one application can resolve + * the intent + * await fdc3.raiseIntent("StartChat", context); + * + * // or find apps to resolve an intent to start a chat with a given contact + * const appIntent = await fdc3.findIntent("StartChat", context); + * // use the metadata of an app or app instance to describe the target app for + * the intent + * await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]); + * + * //Raise an intent without a context by using the null context type + * await fdc3.raiseIntent("StartChat", {type: "fdc3.nothing"}); + * + * //Raise an intent and retrieve a result from the IntentResolution + * let resolution = await agent.raiseIntent("intentName", context); + * try { + * const result = await resolution.getResult(); + * if (result && result.broadcast) { //detect whether the result is Context or a + * Channel + * console.log(`${resolution.source} returned a channel with id ${result.id}`); + * } else if (result){ + * console.log(`${resolution.source} returned data: ${JSON.stringify(result)}`); + * } else { + * console.error(`${resolution.source} didn't return anything` + * } + * } catch(error) { + * console.error(`${resolution.source} returned an error: ${error}`); + * } + * ``` + */ + CompletionStage raiseIntent(String intent, Context context, AppIdentifier app); - /** - * Optional function that joins the app to the specified User channel. In most cases, applications SHOULD be joined to channels via UX provided to the application by the desktop agent, rather than calling this function directly. - * - * If an app is joined to a channel, all `fdc3.broadcast` calls will go to the channel, and all listeners assigned via `fdc3.addContextListener` will listen on the channel. - * - * If the channel already contains context that would be passed to context listeners assed via `fdc3.addContextListener` then those listeners will be called immediately with that context. - * - * An app can only be joined to one channel at a time. - * - * If an error occurs (such as the channel is unavailable or the join request is denied) the promise MUST be rejected with an `Error` Object with a `message` chosen from the `ChannelError` enumeration. - * - * ```javascript - * // get all system channels - * const channels = await fdc3.getUserChannels(); - * // create UI to pick from the User channels - * // join the channel on selection - * fdc3.joinUserChannel(selectedChannel.id); - * ``` - */ - CompletionStage joinUserChannel(String channelId); + default CompletionStage raiseIntent(String intent, Context context) { + return raiseIntent(intent, context, null); + } - /** - * Returns a `Channel` object for the specified channel, creating it (as an _App_ channel) if it does not exist. - * - * If the Channel cannot be created or access was denied, the returned promise MUST be rejected with an `Error` Object with a `message` chosen from the `ChannelError` enumeration. - * - * ```javascript - * try { - * const myChannel = await fdc3.getOrCreateChannel("myChannel"); - * const myChannel.addContextListener(null, context => {}); - * } - * catch (err){ - * //app could not register the channel - * } - * ``` - */ - CompletionStage getOrCreateChannel(String channelId); + /** + * Finds and raises an intent against apps registered with the desktop agent + * based on the type of the specified context data example. + * + * The desktop agent SHOULD first resolve to a specific intent based on the + * provided context if more than one intent is available for the specified + * context. This MAY be achieved by displaying a resolver UI. It SHOULD then + * resolve to a specific app to handle the selected intent and specified + * context. + * Alternatively, the specific app or app instance to target can also be + * provided, in which case the resolver SHOULD only offer intents supported by + * the specified application. + * + * Using `raiseIntentForContext` is similar to calling `findIntentsByContext`, + * and then raising an intent against one of the returned apps, except in this + * case the desktop agent has the opportunity to provide the user with a richer + * selection interface where they can choose both the intent and target app. + * + * Returns an `IntentResolution` object, see `raiseIntent()` for details. + * + * If a target intent and app cannot be found with the criteria provided or the + * user either closes the resolver UI or otherwise cancels resolution, the + * promise MUST be rejected with an `Error` object with a `message` chosen from + * the `ResolveError` enumeration. If a specific target `app` parameter was set, + * but either the app or app instance is not available, the promise MUST be + * rejected with an `Error` object with either the + * `ResolveError.TargetAppUnavailable` or + * `ResolveError.TargetInstanceUnavailable` string as its `message`. + * + * ```javascript + * // Resolve against all intents registered for the type of the specified + * context + * await fdc3.raiseIntentForContext(context); + * + * // Resolve against all intents registered by a specific target app for the + * specified context + * await fdc3.raiseIntentForContext(context, targetAppIdentifier); + * ``` + */ + CompletionStage raiseIntentForContext(Context context, AppIdentifier app); - /** - * Returns a `Channel` with an auto-generated identity that is intended for private communication between applications. Primarily used to create Channels that will be returned to other applications via an `IntentResolution` for a raised intent. - * - * If the `PrivateChannel` cannot be created, the returned promise MUST be rejected with an `Error` object with a `message` chosen from the `ChannelError` enumeration. - * - * The `PrivateChannel` type is provided to support synchronisation of data transmitted over returned channels, by allowing both parties to listen for events denoting subscription and unsubscription from the returned channel. `PrivateChannels` are only retrievable via raising an intent. - * - * * It is intended that Desktop Agent implementations: - * - SHOULD restrict external apps from listening or publishing on this channel. - * - MUST prevent `PrivateChannels` from being retrieved via fdc3.getOrCreateChannel. - * - MUST provide the `id` value for the channel as required by the `Channel` interface. - * - * ```javascript - * fdc3.addIntentListener("QuoteStream", async (context) => { - * const channel: PrivateChannel = await fdc3.createPrivateChannel(); - * const symbol = context.id.ticker; - * - * // This gets called when the remote side adds a context listener - * const addContextListener = channel.onAddContextListener((contextType) => { - * // broadcast price quotes as they come in from our quote feed - * feed.onQuote(symbol, (price) => { - * channel.broadcast({ type: "price", price}); - * }); - * }); - * - * // This gets called when the remote side calls Listener.unsubscribe() - * const unsubscriberListener = channel.onUnsubscribe((contextType) => { - * feed.stop(symbol); - * }); - * - * // This gets called if the remote side closes - * const disconnectListener = channel.onDisconnect(() => { - * feed.stop(symbol); - * }) - * - * return channel; - * }); - * ``` - */ - CompletionStage createPrivateChannel(); + default CompletionStage raiseIntentForContext(Context context) { + return raiseIntentForContext(context, null); + } - /** - * Optional function that returns the `Channel` object for the current User channel membership. In most cases, an application's membership of channels SHOULD be managed via UX provided to the application by the desktop agent, rather than calling this function directly. - * - * Returns `null` if the app is not joined to a channel. - */ - CompletionStage> getCurrentChannel(); + /** + * Adds a listener for incoming Intents from the Agent. The handler function may + * return void or a promise that should resolve to an `IntentResult`, which is + * either a `Context` object, representing any data that should be returned, or + * a `Channel` over which data responses will be sent. The IntentResult will be + * returned to app that raised the intent via the `IntentResolution` and + * retrieved from it using the `getResult()` function. + * + * The Desktop Agent MUST reject the promise returned by the `getResult()` + * function of `IntentResolution` if: (1) the intent handling function's + * returned promise rejects, (2) the intent handling function doesn't return a + * promise, or (3) the returned promise resolves to an invalid type. + * + * The `PrivateChannel` type is provided to support synchronisation of data + * transmitted over returned channels, by allowing both parties to listen for + * events denoting subscription and unsubscription from the returned channel. + * `PrivateChannels` are only retrievable via raising an intent. + * + * Optional metadata about the raised intent, including the app that originated + * the message, SHOULD be provided by the desktop agent implementation. + * + * ```javascript + * //Handle a raised intent + * const listener = fdc3.addIntentListener('StartChat', context => { + * // start chat has been requested by another application + * return; + * }); + * + * //Handle a raised intent and log the originating app metadata + * const listener = fdc3.addIntentListener('StartChat', (contact, metadata) => { + * console.log(`Received intent StartChat\nContext: ${contact}\nOriginating app: + * ${metadata?.source}`); + * return; + * }); + * + * //Handle a raised intent and return Context data via a promise + * fdc3.addIntentListener("CreateOrder", (context) => { + * return new Promise((resolve) => { + * // go create the order + * resolve({type: "fdc3.order", id: { "orderId": 1234}}); + * }); + * }); + * + * //Handle a raised intent and return a Private Channel over which response + * will be sent + * fdc3.addIntentListener("QuoteStream", async (context) => { + * const channel: PrivateChannel = await fdc3.createPrivateChannel(); + * const symbol = context.id.symbol; + * + * // Called when the remote side adds a context listener + * const addContextListener = channel.onAddContextListener((contextType) => { + * // broadcast price quotes as they come in from our quote feed + * feed.onQuote(symbol, (price) => { + * channel.broadcast({ type: "price", price}); + * }); + * }); + * + * // Stop the feed if the remote side closes + * const disconnectListener = channel.onDisconnect(() => { + * feed.stop(symbol); + * }); + * + * return channel; + * }); + * ``` + */ + CompletionStage addIntentListener(String intent, IntentHandler handler); - /** - * Optional function that removes the app from any User channel membership. In most cases, an application's membership of channels SHOULD be managed via UX provided to the application by the desktop agent, rather than calling this function directly. - * - * Context broadcast and listening through the top-level `fdc3.broadcast` and `fdc3.addContextListener` will be a no-op when the app is not on a channel. - * - * ```javascript - * //desktop-agent scope context listener - * const fdc3Listener = fdc3.addContextListener(null, context => {}); - * await fdc3.leaveCurrentChannel(); - * //the fdc3Listener will now cease receiving context - * //listening on a specific channel, retrieved via fdc3.getOrCreateChannel(), will continue to work: - * redChannel.addContextListener(null, channelListener); - * ``` - */ - CompletionStage leaveCurrentChannel(); + /** + * Adds a listener for incoming context broadcasts from the Desktop Agent via + * User channels. If the consumer is only interested in a context of a + * particular type, they can they can specify that type. If the consumer is able + * to receive context of any type or will inspect types received, then they can + * pass `null` as the `contextType` parameter to receive all context types. + * + * Context broadcasts are only received from apps that are joined to the same + * User channel as the listening application, hence, if the application is not + * currently joined to a channel no broadcasts will be received. If this + * function is called after the app has already joined a channel and the channel + * already contains context that would be passed to the context listener, then + * it will be called immediately with that context. + * + * Optional metadata about the context message, including the app that + * originated the message, SHOULD be provided by the desktop agent + * implementation. + * + * ```javascript + * // any context + * const listener = await fdc3.addContextListener(null, context => { ... }); + * + * // listener for a specific type + * const contactListener = await fdc3.addContextListener('fdc3.contact', contact + * => { ... }); + * + * // listener that logs metadata for the message a specific type + * const contactListener = await fdc3.addContextListener('fdc3.contact', + * (contact, metadata) => { + * console.log(`Received context message\nContext: ${contact}\nOriginating app: + * ${metadata?.source}`); + * //do something else with the context + * }); + * ``` + */ + CompletionStage addContextListener(String contextType, ContextHandler handler); - /** - * Retrieves information about the FDC3 Desktop Agent implementation, including the supported version - * of the FDC3 specification, the name of the provider of the implementation, its own version number - * and the metadata of the calling application according to the desktop agent. - * - * Returns an `ImplementationMetadata` object. This metadata object can be used to vary the behavior - * of an application based on the version supported by the Desktop Agent and for logging purposes. - * - * ```js - * import {compareVersionNumbers, versionIsAtLeast} from '@finos/fdc3'; - * - * if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) { - * await fdc3.raiseIntentForContext(context); - * } else { - * await fdc3.raiseIntent("ViewChart", context); - * } - * ``` - * - * The `ImplementationMetadata` object returned also includes the metadata for the calling application, - * according to the Desktop Agent. This allows the application to retrieve its own `appId`, `instanceId` - * and other details, e.g.: - * - * ```js - * let implementationMetadata = await fdc3.getInfo(); - * let {appId, instanceId} = implementationMetadata.appMetadata; - * ``` - */ - CompletionStage getInfo(); + /** + * Registers a handler for non-context and non-intent events from the Desktop + * Agent. + * If the consumer is only interested in an event of a particular type, they can + * specify that type. + * If the consumer is able to receive events of any type or will inspect types + * received, + * then they can pass `null` as the `type` parameter to receive all event types. + * + * Whenever the handler function is called it will be passed an event object + * with details + * related to the event. + * + * ```javascript + * // listener for all events + * const listener = await fdc3.addEventListener(null, event => { ... }); + * + * // listener for a specific event type + * const channelChangedListener = await + * fdc3.addEventListener('userChannelChanged', event => { ... }); + * ``` + * + * @param type the type of event to listen for (e.g., "userChannelChanged"), + * or null to receive all event types + * @param handler the handler function to be called when matching events occur + * @return a Listener object that can be used to unsubscribe from the events + */ + CompletionStage addEventListener(String type, EventHandler handler); - /** - * Retrieves the `AppMetadata` for an `AppIdentifier`, which provides additional metadata (such as icons, - * a title and description) from the App Directory record for the application, that may be used for display - * purposes. - * - * ```js - * let appIdentifier = { appId: "MyAppId@my.appd.com" } - * let appMetadata = await fdc3.getAppMetadata(appIdentifier); - * ``` - */ - CompletionStage getAppMetadata(AppIdentifier app); + /** + * Retrieves a list of the User channels available for the app to join. + */ + CompletionStage> getUserChannels(); + + /** + * Optional function that joins the app to the specified User channel. In most + * cases, applications SHOULD be joined to channels via UX provided to the + * application by the desktop agent, rather than calling this function directly. + * + * If an app is joined to a channel, all `fdc3.broadcast` calls will go to the + * channel, and all listeners assigned via `fdc3.addContextListener` will listen + * on the channel. + * + * If the channel already contains context that would be passed to context + * listeners assed via `fdc3.addContextListener` then those listeners will be + * called immediately with that context. + * + * An app can only be joined to one channel at a time. + * + * If an error occurs (such as the channel is unavailable or the join request is + * denied) the promise MUST be rejected with an `Error` Object with a `message` + * chosen from the `ChannelError` enumeration. + * + * ```javascript + * // get all system channels + * const channels = await fdc3.getUserChannels(); + * // create UI to pick from the User channels + * // join the channel on selection + * fdc3.joinUserChannel(selectedChannel.id); + * ``` + */ + CompletionStage joinUserChannel(String channelId); + + /** + * Returns a `Channel` object for the specified channel, creating it (as an + * _App_ channel) if it does not exist. + * + * If the Channel cannot be created or access was denied, the returned promise + * MUST be rejected with an `Error` Object with a `message` chosen from the + * `ChannelError` enumeration. + * + * ```javascript + * try { + * const myChannel = await fdc3.getOrCreateChannel("myChannel"); + * const myChannel.addContextListener(null, context => {}); + * } + * catch (err){ + * //app could not register the channel + * } + * ``` + */ + CompletionStage getOrCreateChannel(String channelId); + + /** + * Returns a `Channel` with an auto-generated identity that is intended for + * private communication between applications. Primarily used to create Channels + * that will be returned to other applications via an `IntentResolution` for a + * raised intent. + * + * If the `PrivateChannel` cannot be created, the returned promise MUST be + * rejected with an `Error` object with a `message` chosen from the + * `ChannelError` enumeration. + * + * The `PrivateChannel` type is provided to support synchronisation of data + * transmitted over returned channels, by allowing both parties to listen for + * events denoting subscription and unsubscription from the returned channel. + * `PrivateChannels` are only retrievable via raising an intent. + * + * * It is intended that Desktop Agent implementations: + * - SHOULD restrict external apps from listening or publishing on this channel. + * - MUST prevent `PrivateChannels` from being retrieved via + * fdc3.getOrCreateChannel. + * - MUST provide the `id` value for the channel as required by the `Channel` + * interface. + * + * ```javascript + * fdc3.addIntentListener("QuoteStream", async (context) => { + * const channel: PrivateChannel = await fdc3.createPrivateChannel(); + * const symbol = context.id.ticker; + * + * // This gets called when the remote side adds a context listener + * const addContextListener = channel.onAddContextListener((contextType) => { + * // broadcast price quotes as they come in from our quote feed + * feed.onQuote(symbol, (price) => { + * channel.broadcast({ type: "price", price}); + * }); + * }); + * + * // This gets called when the remote side calls Listener.unsubscribe() + * const unsubscriberListener = channel.onUnsubscribe((contextType) => { + * feed.stop(symbol); + * }); + * + * // This gets called if the remote side closes + * const disconnectListener = channel.onDisconnect(() => { + * feed.stop(symbol); + * }) + * + * return channel; + * }); + * ``` + */ + CompletionStage createPrivateChannel(); + + /** + * Optional function that returns the `Channel` object for the current User + * channel membership. In most cases, an application's membership of channels + * SHOULD be managed via UX provided to the application by the desktop agent, + * rather than calling this function directly. + * + * Returns `null` if the app is not joined to a channel. + */ + CompletionStage> getCurrentChannel(); + + /** + * Optional function that removes the app from any User channel membership. In + * most cases, an application's membership of channels SHOULD be managed via UX + * provided to the application by the desktop agent, rather than calling this + * function directly. + * + * Context broadcast and listening through the top-level `fdc3.broadcast` and + * `fdc3.addContextListener` will be a no-op when the app is not on a channel. + * + * ```javascript + * //desktop-agent scope context listener + * const fdc3Listener = fdc3.addContextListener(null, context => {}); + * await fdc3.leaveCurrentChannel(); + * //the fdc3Listener will now cease receiving context + * //listening on a specific channel, retrieved via fdc3.getOrCreateChannel(), + * will continue to work: + * redChannel.addContextListener(null, channelListener); + * ``` + */ + CompletionStage leaveCurrentChannel(); + + /** + * Retrieves information about the FDC3 Desktop Agent implementation, including + * the supported version + * of the FDC3 specification, the name of the provider of the implementation, + * its own version number + * and the metadata of the calling application according to the desktop agent. + * + * Returns an `ImplementationMetadata` object. This metadata object can be used + * to vary the behavior + * of an application based on the version supported by the Desktop Agent and for + * logging purposes. + * + * ```js + * import {compareVersionNumbers, versionIsAtLeast} from '@finos/fdc3'; + * + * if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) { + * await fdc3.raiseIntentForContext(context); + * } else { + * await fdc3.raiseIntent("ViewChart", context); + * } + * ``` + * + * The `ImplementationMetadata` object returned also includes the metadata for + * the calling application, + * according to the Desktop Agent. This allows the application to retrieve its + * own `appId`, `instanceId` + * and other details, e.g.: + * + * ```js + * let implementationMetadata = await fdc3.getInfo(); + * let {appId, instanceId} = implementationMetadata.appMetadata; + * ``` + */ + CompletionStage getInfo(); + + /** + * Retrieves the `AppMetadata` for an `AppIdentifier`, which provides additional + * metadata (such as icons, + * a title and description) from the App Directory record for the application, + * that may be used for display + * purposes. + * + * ```js + * let appIdentifier = { appId: "MyAppId@my.appd.com" } + * let appMetadata = await fdc3.getAppMetadata(appIdentifier); + * ``` + */ + CompletionStage getAppMetadata(AppIdentifier app); } diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java b/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java new file mode 100644 index 00000000..7c5f5e19 --- /dev/null +++ b/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java @@ -0,0 +1,34 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.api.types; + +/** + * Describes a callback that handles non-context and non-intent events from the Desktop Agent. + * Used when attaching listeners via {@code addEventListener}. + */ +@FunctionalInterface +public interface EventHandler { + + /** + * Handles an FDC3 event. + * + * @param event the event object containing type and details + */ + void handleEvent(FDC3Event event); + +} + diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java b/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java new file mode 100644 index 00000000..1cc870a8 --- /dev/null +++ b/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java @@ -0,0 +1,56 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.api.types; + +/** + * Type representing the event object passed to event handlers subscribed to + * FDC3 events via the {@code addEventListener} method. + * + * Events will always include both {@code type} and {@code details} properties, + * which describe the type of event and any additional details, respectively. + * + * @param The type of the details object for this event + */ +public class FDC3Event { + + private final String type; + private final T details; + + public FDC3Event(String type, T details) { + this.type = type; + this.details = details; + } + + /** + * Returns the type of the event. + * + * @return the event type string (e.g., "userChannelChanged") + */ + public String getType() { + return type; + } + + /** + * Returns the details object containing additional information about the event. + * + * @return the event details + */ + public T getDetails() { + return details; + } +} + From 209f488b8d8889d1e23368159cce1218843ab471 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 10 Dec 2025 10:09:00 +0000 Subject: [PATCH 02/65] Added cucumber testing support for Java --- fdc3-testing/pom.xml | 136 +++++++ .../com/finos/fdc3/testing/FDC3Testing.java | 106 ++++++ .../fdc3/testing/agent/ChannelSelector.java | 63 ++++ .../fdc3/testing/agent/IntentResolver.java | 68 ++++ .../testing/agent/SimpleChannelSelector.java | 78 ++++ .../testing/agent/SimpleIntentResolver.java | 118 ++++++ .../com/finos/fdc3/testing/package-info.java | 20 + .../fdc3/testing/steps/GenericSteps.java | 349 ++++++++++++++++++ .../fdc3/testing/support/MatchingUtils.java | 284 ++++++++++++++ .../finos/fdc3/testing/world/PropsWorld.java | 83 +++++ .../finos/fdc3/api/types/EventHandler.java | 4 +- .../com/finos/fdc3/api/types/FDC3Event.java | 1 - pom.xml | 3 +- 13 files changed, 1308 insertions(+), 5 deletions(-) create mode 100644 fdc3-testing/pom.xml create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java create mode 100644 fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml new file mode 100644 index 00000000..fb9aa58a --- /dev/null +++ b/fdc3-testing/pom.xml @@ -0,0 +1,136 @@ + + + + + + 4.0.0 + + com.finos.fdc3.api + finos-bmo-hackathon + 1.0.0-SNAPSHOT + + + fdc3-testing + FDC3 Cucumber Testing Framework + Cucumber step definitions and utilities for testing FDC3 implementations + + + UTF-8 + 11 + 11 + 7.15.0 + 5.10.1 + 2.16.1 + 2.9.0 + 1.3.1 + + + + + + com.finos.fdc3.api + fdc3api + ${project.version} + + + + + io.cucumber + cucumber-java + ${cucumber.version} + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + + + + + org.junit.platform + junit-platform-suite + 1.10.1 + + + org.junit.jupiter + junit-jupiter + ${junit.version} + + + + + com.jayway.jsonpath + json-path + ${jsonpath.version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + com.networknt + json-schema-validator + ${networknt.version} + + + + + org.slf4j + slf4j-api + 2.0.9 + + + + + + + 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 + + + + + + + + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java new file mode 100644 index 00000000..f134a26c --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java @@ -0,0 +1,106 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing; + +import com.finos.fdc3.testing.agent.ChannelSelector; +import com.finos.fdc3.testing.agent.IntentResolver; +import com.finos.fdc3.testing.agent.SimpleChannelSelector; +import com.finos.fdc3.testing.agent.SimpleIntentResolver; +import com.finos.fdc3.testing.steps.GenericSteps; +import com.finos.fdc3.testing.support.MatchingUtils; +import com.finos.fdc3.testing.world.PropsWorld; + +/** + * Main entry point for the FDC3 Testing framework. + *

+ * This class provides convenient access to all testing components and constants. + *

+ * Usage example: + *

+ * // Create a test world
+ * PropsWorld world = new PropsWorld();
+ *
+ * // Create step definitions (automatically registered with Cucumber)
+ * GenericSteps steps = new GenericSteps(world);
+ *
+ * // Use matching utilities
+ * Object value = MatchingUtils.handleResolve("{result.field}", world);
+ *
+ * // Create test agents
+ * IntentResolver resolver = new SimpleIntentResolver(world);
+ * ChannelSelector selector = new SimpleChannelSelector(world);
+ * 
+ */ +public final class FDC3Testing { + + /** + * Constant for channel state key in PropsWorld. + */ + public static final String CHANNEL_STATE = SimpleChannelSelector.CHANNEL_STATE; + + private FDC3Testing() { + // Utility class - prevent instantiation + } + + /** + * Create a new PropsWorld instance for test state. + * + * @return a new PropsWorld + */ + public static PropsWorld createWorld() { + return new PropsWorld(); + } + + /** + * Create a new SimpleIntentResolver for testing. + * + * @param world the PropsWorld to use + * @return a new SimpleIntentResolver + */ + public static IntentResolver createIntentResolver(PropsWorld world) { + return new SimpleIntentResolver(world); + } + + /** + * Create a new SimpleChannelSelector for testing. + * + * @param world the PropsWorld to use + * @return a new SimpleChannelSelector + */ + public static ChannelSelector createChannelSelector(PropsWorld world) { + return new SimpleChannelSelector(world); + } + + /** + * Get the MatchingUtils class for static utility access. + * + * @return the MatchingUtils class + */ + public static Class getMatchingUtils() { + return MatchingUtils.class; + } + + /** + * Get the GenericSteps class for step definition registration. + * + * @return the GenericSteps class + */ + public static Class getGenericSteps() { + return GenericSteps.class; + } +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java new file mode 100644 index 00000000..3c926849 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java @@ -0,0 +1,63 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.agent; + +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import com.finos.fdc3.api.channel.Channel; + +/** + * Interface for selecting channels. + *

+ * Implementations of this interface handle the user interaction + * for selecting a channel from available options. + */ +public interface ChannelSelector { + + /** + * Update the current channel state. + * + * @param channelId the current channel ID, or null if not joined + * @param availableChannels the list of available channels + * @return a CompletionStage that completes when updated + */ + CompletionStage updateChannel(String channelId, List availableChannels); + + /** + * Set a callback to be invoked when the user changes channels. + * + * @param callback the callback, accepting the new channel ID (or null to leave) + */ + void setChannelChangeCallback(Consumer callback); + + /** + * Connect the selector (e.g., initialize UI components). + * + * @return a CompletionStage that completes when connected + */ + CompletionStage connect(); + + /** + * Disconnect the selector (e.g., cleanup UI components). + * + * @return a CompletionStage that completes when disconnected + */ + CompletionStage disconnect(); +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java new file mode 100644 index 00000000..82101ab0 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java @@ -0,0 +1,68 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.agent; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.AppIntent; +import com.finos.fdc3.api.types.IntentResult; + +/** + * Interface for resolving intents to specific applications. + *

+ * Implementations of this interface handle the user interaction + * for selecting an intent and target application when multiple + * options are available. + */ +public interface IntentResolver { + + /** + * Connect the resolver (e.g., initialize UI components). + * + * @return a CompletionStage that completes when connected + */ + CompletionStage connect(); + + /** + * Disconnect the resolver (e.g., cleanup UI components). + * + * @return a CompletionStage that completes when disconnected + */ + CompletionStage disconnect(); + + /** + * Called when an intent has been chosen and resolved. + * + * @param intentResult the result of the intent resolution + * @return a CompletionStage containing the intent result + */ + CompletionStage intentChosen(IntentResult intentResult); + + /** + * Choose an intent from a list of available intents. + * + * @param appIntents the list of available app intents + * @param context the context for the intent + * @return a CompletionStage containing the chosen resolution, or empty if cancelled + */ + CompletionStage> chooseIntent( + List appIntents, Context context); +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java new file mode 100644 index 00000000..0abdb581 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java @@ -0,0 +1,78 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.agent; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.testing.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; + + public SimpleChannelSelector(PropsWorld world) { + this.world = world; + } + + @Override + public CompletionStage updateChannel(String channelId, List availableChannels) { + world.set("channelId", channelId); + world.set("channels", availableChannels); + return CompletableFuture.completedFuture(null); + } + + @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 simulateChannelChange(String channelId) { + if (channelChangeCallback != null) { + channelChangeCallback.accept(channelId); + } + } + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java new file mode 100644 index 00000000..f1be263d --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java @@ -0,0 +1,118 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.agent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.AppIntent; +import com.finos.fdc3.api.metadata.AppMetadata; +import com.finos.fdc3.api.metadata.IntentMetadata; +import com.finos.fdc3.api.types.IntentResult; +import com.finos.fdc3.testing.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 cancels. + *

+ * 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 connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage intentChosen(IntentResult intentResult) { + world.set("intent-result", intentResult); + return CompletableFuture.completedFuture(intentResult); + } + + @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(Optional.empty()); + } + + // Select the first intent and first app + AppIntent firstIntent = appIntents.get(0); + IntentMetadata intent = firstIntent.getIntent(); + List apps = new ArrayList<>(firstIntent.getApps()); + AppMetadata firstApp = apps.get(0); + + // Store the resolution for testing + IntentResolutionChoice resolution = new IntentResolutionChoice( + firstApp.getAppId(), + firstApp.getInstanceId().orElse(null), + intent.getName() + ); + + world.set("intent-resolution", resolution); + + return CompletableFuture.completedFuture(Optional.of(resolution)); + } + + /** + * Represents a choice made during intent resolution. + */ + public static class IntentResolutionChoice { + private final String appId; + private final String instanceId; + private final String intent; + + public IntentResolutionChoice(String appId, String instanceId, String intent) { + this.appId = appId; + this.instanceId = instanceId; + this.intent = intent; + } + + public String getAppId() { + return appId; + } + + public String getInstanceId() { + return instanceId; + } + + public String getIntent() { + return intent; + } + } +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java new file mode 100644 index 00000000..89457dc2 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java @@ -0,0 +1,20 @@ +/** + * FDC3 Cucumber Testing Framework. + *

+ * This package provides Cucumber step definitions and utilities for testing FDC3 implementations. + * It is designed to allow reuse of Cucumber feature files from the JavaScript FDC3 repository + * in Java implementations. + *

+ * Main components: + *

    + *
  • {@link com.finos.fdc3.testing.world.PropsWorld} - Cucumber World class for test state
  • + *
  • {@link com.finos.fdc3.testing.steps.GenericSteps} - Generic Cucumber step definitions
  • + *
  • {@link com.finos.fdc3.testing.support.MatchingUtils} - Utilities for matching test data
  • + *
  • {@link com.finos.fdc3.testing.agent.SimpleIntentResolver} - Simple intent resolver for testing
  • + *
  • {@link com.finos.fdc3.testing.agent.SimpleChannelSelector} - Simple channel selector for testing
  • + *
+ * + * @see FDC3 Standard + */ +package com.finos.fdc3.testing; + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java new file mode 100644 index 00000000..36e43396 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java @@ -0,0 +1,349 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.steps; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.finos.fdc3.testing.support.MatchingUtils; +import com.finos.fdc3.testing.world.PropsWorld; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static com.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; +import static com.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static com.finos.fdc3.testing.support.MatchingUtils.matchData; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Generic Cucumber step definitions for FDC3 testing. + * This is equivalent to the TypeScript generic.steps.ts module. + */ +public class GenericSteps { + + private final PropsWorld world; + + public GenericSteps(PropsWorld world) { + this.world = world; + } + + // ========== Promise Resolution Steps ========== + + @Then("the promise {string} should resolve") + public void thePromiseShouldResolve(String field) { + try { + Object promise = handleResolve(field, world); + Object result = resolvePromise(promise); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @Then("the promise {string} should resolve within 10 seconds") + public void thePromiseShouldResolveWithin10Seconds(String field) throws Exception { + try { + Object promise = handleResolve(field, world); + Object result = resolvePromiseWithTimeout(promise, 10, TimeUnit.SECONDS); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + // ========== Method Invocation Steps ========== + + @When("I call {string} with {string}") + public void iCallWith(String field, String fnName) { + try { + Object object = handleResolve(field, world); + Object result = invokeMethod(object, fnName); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} with parameter {string}") + public void iCallWithParameter(String field, String fnName, String param) { + try { + Object object = handleResolve(field, world); + Object paramValue = handleResolve(param, world); + Object result = invokeMethod(object, fnName, paramValue); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} with parameters {string} and {string}") + public void iCallWithTwoParameters(String field, String fnName, String param1, String param2) { + try { + Object object = handleResolve(field, world); + Object paramValue1 = handleResolve(param1, world); + Object paramValue2 = handleResolve(param2, world); + Object result = invokeMethod(object, fnName, paramValue1, paramValue2); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} with parameters {string} and {string} and {string}") + public void iCallWithThreeParameters(String field, String fnName, String param1, String param2, String param3) { + try { + Object object = handleResolve(field, world); + Object paramValue1 = handleResolve(param1, world); + Object paramValue2 = handleResolve(param2, world); + Object paramValue3 = handleResolve(param3, world); + Object result = invokeMethod(object, fnName, paramValue1, paramValue2, paramValue3); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + // ========== Reference/Alias Steps ========== + + @When("I refer to {string} as {string}") + public void iReferToAs(String from, String to) { + world.set(to, handleResolve(from, world)); + } + + // ========== Array Matching Steps ========== + + @Then("{string} is an array of objects with the following contents") + public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { + @SuppressWarnings("unchecked") + List data = (List) handleResolve(field, world); + matchData(world, data, dt); + } + + @Then("{string} is an array of objects with length {string}") + public void isAnArrayOfObjectsWithLength(String field, String lengthField) { + @SuppressWarnings("unchecked") + List data = (List) handleResolve(field, world); + int expectedLength = ((Number) handleResolve(lengthField, world)).intValue(); + assertEquals(expectedLength, data.size()); + } + + @Then("{string} is an array of strings with the following values") + public void isAnArrayOfStringsWithValues(String field, DataTable dt) { + @SuppressWarnings("unchecked") + List data = (List) handleResolve(field, world); + List> values = data.stream() + .map(s -> Map.of("value", s)) + .collect(Collectors.toList()); + matchData(world, values, dt); + } + + // ========== Object Matching Steps ========== + + @Then("{string} is an object with the following contents") + public void isAnObjectWithContents(String field, DataTable params) { + List> table = params.asMaps(); + Object data = handleResolve(field, world); + assertTrue(doesRowMatch(world, table.get(0), data)); + } + + // ========== Null/Boolean/Undefined Checks ========== + + @Then("{string} is null") + public void isNull(String field) { + assertNull(handleResolve(field, world)); + } + + @Then("{string} is not null") + public void isNotNull(String field) { + assertNotNull(handleResolve(field, world)); + } + + @Then("{string} is true") + public void isTrue(String field) { + Object value = handleResolve(field, world); + assertTrue(isTruthy(value)); + } + + @Then("{string} is false") + public void isFalse(String field) { + Object value = handleResolve(field, world); + assertFalse(isTruthy(value)); + } + + @Then("{string} is undefined") + public void isUndefined(String field) { + // In Java, we treat undefined as null or non-existent in the props map + Object value = handleResolve(field, world); + assertNull(value); + } + + @Then("{string} is empty") + public void isEmpty(String field) { + @SuppressWarnings("unchecked") + List data = (List) handleResolve(field, world); + assertTrue(data.isEmpty()); + } + + // ========== Equality Check ========== + + @Then("{string} is {string}") + public void fieldIsValue(String field, String expected) { + Object fieldValue = handleResolve(field, world); + Object expectedValue = handleResolve(expected, world); + assertEquals(String.valueOf(expectedValue), String.valueOf(fieldValue)); + } + + // ========== Error Checks ========== + + @Then("{string} is an error with message {string}") + public void isAnErrorWithMessage(String field, String errorType) { + Object value = handleResolve(field, world); + assertTrue(value instanceof Throwable); + assertEquals(errorType, ((Throwable) value).getMessage()); + } + + @Then("{string} is an error") + public void isAnError(String field) { + Object value = handleResolve(field, world); + assertTrue(value instanceof Throwable); + } + + // ========== Invocation Counter ========== + + @Given("{string} is a invocation counter into {string}") + public void isAnInvocationCounter(String handlerName, String counterField) { + world.set(counterField, 0); + world.set(handlerName, (Runnable) () -> { + int amount = (Integer) world.get(counterField); + amount++; + world.set(counterField, amount); + }); + } + + // ========== Function Creation ========== + + @Given("{string} is a function which returns a promise of {string}") + public void isAFunctionReturningPromise(String fnName, String valueField) { + Object value = handleResolve(valueField, world); + world.set(fnName, (Supplier>) () -> + CompletableFuture.completedFuture(value)); + } + + // ========== Wait Step ========== + + @Given("we wait for a period of {string} ms") + public void weWaitForPeriod(String ms) throws InterruptedException { + Thread.sleep(Long.parseLong(ms)); + } + + // ========== Schema Loading ========== + + @Given("schemas loaded") + public void schemasLoaded() { + // Schema loading would be configured externally + // The schemas map should be set up in the test configuration + world.log("Schemas should be loaded by test configuration"); + } + + // ========== Helper Methods ========== + + /** + * Resolve a promise/CompletionStage to its value. + */ + private Object resolvePromise(Object promise) throws Exception { + if (promise instanceof CompletionStage) { + return ((CompletionStage) promise).toCompletableFuture().get(); + } else if (promise instanceof CompletableFuture) { + return ((CompletableFuture) promise).get(); + } + return promise; + } + + /** + * Resolve a promise with a timeout. + */ + private Object resolvePromiseWithTimeout(Object promise, long timeout, TimeUnit unit) throws Exception { + if (promise instanceof CompletionStage) { + return ((CompletionStage) promise).toCompletableFuture().get(timeout, unit); + } else if (promise instanceof CompletableFuture) { + return ((CompletableFuture) promise).get(timeout, unit); + } + return promise; + } + + /** + * Invoke a method on an object by name. + */ + private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { + Class[] paramTypes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + paramTypes[i] = args[i] != null ? args[i].getClass() : Object.class; + } + + Method method = findMethod(target.getClass(), methodName, args.length); + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + + method.setAccessible(true); + Object result = method.invoke(target, args); + + // If the result is a CompletionStage, resolve it + return resolvePromise(result); + } + + /** + * Find a method by name and parameter count. + */ + private Method findMethod(Class clazz, String name, int paramCount) { + for (Method method : clazz.getMethods()) { + if (method.getName().equals(name) && method.getParameterCount() == paramCount) { + return method; + } + } + return null; + } + + /** + * Check if a value is truthy (JavaScript-style). + */ + private boolean isTruthy(Object value) { + if (value == null) { + return false; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue() != 0; + } + if (value instanceof String) { + return !((String) value).isEmpty(); + } + return true; + } +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java new file mode 100644 index 00000000..97b41b64 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java @@ -0,0 +1,284 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.finos.fdc3.testing.world.PropsWorld; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; + +import io.cucumber.datatable.DataTable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Utility class for matching and resolving test data. + * This is equivalent to the TypeScript matching.ts module. + */ +public final class MatchingUtils { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private MatchingUtils() { + // Utility class + } + + /** + * Resolve a field reference to its actual value. + *

+ * If the name is wrapped in braces like {field.path}, it will be resolved + * using JSONPath against the PropsWorld props. Special values like {null}, + * {true}, {false}, and numeric values are handled specially. + * + * @param name the field reference or literal value + * @param world the PropsWorld containing test state + * @return the resolved value + */ + public static Object handleResolve(String name, PropsWorld world) { + if (name.startsWith("{") && name.endsWith("}")) { + String stripped = name.substring(1, name.length() - 1); + + // Handle special values + if ("null".equals(stripped)) { + return null; + } else if ("true".equals(stripped)) { + return true; + } else if ("false".equals(stripped)) { + return false; + } else if (isNumeric(stripped)) { + return Double.parseDouble(stripped); + } else { + // Use JSONPath to resolve the value from props + try { + Object propsAsJson = objectMapper.convertValue(world.getProps(), Object.class); + String jsonPath = "$." + stripped; + return JsonPath.read(propsAsJson, jsonPath); + } catch (PathNotFoundException e) { + return null; + } + } + } else { + return name; + } + } + + /** + * Check if a string is numeric. + * + * @param str the string to check + * @return true if the string represents a number + */ + private static boolean isNumeric(String str) { + try { + Double.parseDouble(str); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Check if a table row matches the given data object. + * + * @param world the PropsWorld for resolving references + * @param row the table row as a map of field names to expected values + * @param data the actual data object to match against + * @return true if the row matches the data + */ + public static boolean doesRowMatch(PropsWorld world, Map row, Object data) { + for (Map.Entry entry : row.entrySet()) { + String field = entry.getKey(); + String expected = entry.getValue(); + + if (field.endsWith("matches_type")) { + // Schema validation mode + Object valData = data; + + if (field.length() > "matches_type".length()) { + // Extract path before matches_type + String path = field.substring(0, field.length() - "matches_type".length() - 1); + try { + String dataJson = objectMapper.writeValueAsString(data); + valData = JsonPath.read(dataJson, "$." + path); + } catch (Exception e) { + world.log("Error extracting path: " + e.getMessage()); + return false; + } + } + + // Validate against schema + @SuppressWarnings("unchecked") + Map schemas = (Map) world.get("schemas"); + if (schemas == null) { + world.log("No schemas loaded"); + return false; + } + + JsonSchema schema = schemas.get(expected); + if (schema == null) { + world.log("No schema found for " + expected); + return false; + } + + try { + JsonNode dataNode = objectMapper.valueToTree(valData); + var errors = schema.validate(dataNode); + if (!errors.isEmpty()) { + world.log("Validation failed: " + errors); + return false; + } + } catch (Exception e) { + world.log("Validation error: " + e.getMessage()); + return false; + } + } else { + // Field value comparison using JSONPath + try { + String dataJson = objectMapper.writeValueAsString(data); + Object found = JsonPath.read(dataJson, "$." + field); + Object resolved = handleResolve(expected, world); + + if (!Objects.equals(asString(found), asString(resolved))) { + world.log(String.format( + "Match failed on %s: '%s' vs '%s'", field, found, resolved)); + return false; + } + } catch (PathNotFoundException e) { + world.log("Path not found: " + field); + return false; + } catch (JsonProcessingException e) { + world.log("JSON processing error: " + e.getMessage()); + return false; + } + } + } + + return true; + } + + /** + * Convert a value to string for comparison. + */ + private static String asString(Object value) { + return value == null ? null : String.valueOf(value); + } + + /** + * Find the index of a matching row in the list. + * + * @param world the PropsWorld for resolving references + * @param rows the list of expected rows + * @param data the actual data object to find + * @return the index of the matching row, or -1 if not found + */ + public static int indexOf(PropsWorld world, List> rows, Object data) { + for (int i = 0; i < rows.size(); i++) { + if (doesRowMatch(world, rows.get(i), data)) { + return i; + } + } + return -1; + } + + /** + * Match an array of data against a Cucumber DataTable. + * + * @param world the PropsWorld for resolving references + * @param actual the actual array data + * @param dt the expected DataTable + */ + public static void matchData(PropsWorld world, List actual, DataTable dt) { + List> tableData = dt.asMaps(); + int rowCount = tableData.size(); + + world.log(String.format("result %s length %d", + formatJson(actual), actual.size())); + + assertEquals(rowCount, actual.size(), + "Array length mismatch"); + + List resultCopy = new ArrayList<>(actual); + List unmatched = new ArrayList<>(); + + int row = 0; + for (Object item : resultCopy) { + Map matchingRow = tableData.get(row); + row++; + if (!doesRowMatch(world, matchingRow, item)) { + world.log("Couldn't match row: " + formatJson(item)); + unmatched.add(item); + } + } + + assertTrue(unmatched.isEmpty(), + "Some rows could not be matched: " + formatJson(unmatched)); + } + + /** + * Format an object as JSON for logging. + */ + private static String formatJson(Object obj) { + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (JsonProcessingException e) { + return String.valueOf(obj); + } + } + + /** + * Load JSON schemas from a directory. + * + * @param schemaDir the directory containing schema files + * @return a map of schema names to JsonSchema objects + */ + public static Map loadSchemas(String schemaDir) { + Map schemas = new java.util.HashMap<>(); + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + + java.io.File dir = new java.io.File(schemaDir); + if (dir.exists() && dir.isDirectory()) { + java.io.File[] files = dir.listFiles((d, name) -> name.endsWith(".json")); + if (files != null) { + for (java.io.File file : files) { + try { + String content = java.nio.file.Files.readString(file.toPath()); + JsonSchema schema = factory.getSchema(content); + String schemaName = file.getName().replace(".schema.json", ""); + schemas.put(schemaName, schema); + } catch (Exception e) { + // Log and continue + } + } + } + } + + return schemas; + } +} + diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java b/fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java new file mode 100644 index 00000000..b4a07fb8 --- /dev/null +++ b/fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java @@ -0,0 +1,83 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.testing.world; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Cucumber World class that holds test state in a props map. + * This is equivalent to the TypeScript PropsWorld class. + */ +public class PropsWorld { + + private static final Logger logger = LoggerFactory.getLogger(PropsWorld.class); + + private final Map props = new HashMap<>(); + + /** + * Get the props map containing all test state. + * + * @return the props map + */ + public Map getProps() { + return props; + } + + /** + * Get a property value by key. + * + * @param key the property key + * @return the property value, or null if not found + */ + public Object get(String key) { + return props.get(key); + } + + /** + * Set a property value. + * + * @param key the property key + * @param value the property value + */ + public void set(String key, Object value) { + props.put(key, value); + } + + /** + * Check if a property exists. + * + * @param key the property key + * @return true if the property exists + */ + public boolean has(String key) { + return props.containsKey(key); + } + + /** + * Log a message (equivalent to cw.log in TypeScript). + * + * @param message the message to log + */ + public void log(String message) { + logger.info(message); + } +} + diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java b/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java index 7c5f5e19..2490100c 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java @@ -17,7 +17,8 @@ package com.finos.fdc3.api.types; /** - * Describes a callback that handles non-context and non-intent events from the Desktop Agent. + * Describes a callback that handles non-context and non-intent events from the + * Desktop Agent. * Used when attaching listeners via {@code addEventListener}. */ @FunctionalInterface @@ -31,4 +32,3 @@ public interface EventHandler { void handleEvent(FDC3Event event); } - diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java b/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java index 1cc870a8..4a4efef7 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java @@ -53,4 +53,3 @@ public T getDetails() { return details; } } - diff --git a/pom.xml b/pom.xml index 8f5c96d2..a26eea76 100644 --- a/pom.xml +++ b/pom.xml @@ -26,8 +26,7 @@ http://maven.apache.org fdc3api - client - openfin-fdc3-adapter + fdc3-testing 11 From 17fd129f57e463c092557027544fb30fb2d2803a Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 10 Dec 2025 10:40:05 +0000 Subject: [PATCH 03/65] Added implementation of fdc3-agent-proxy --- fdc3-agent-proxy/pom.xml | 130 ++++++ .../com/finos/fdc3/proxy/Connectable.java | 40 ++ .../finos/fdc3/proxy/DesktopAgentProxy.java | 204 +++++++++ .../java/com/finos/fdc3/proxy/Messaging.java | 105 +++++ .../com/finos/fdc3/proxy/apps/AppSupport.java | 64 +++ .../fdc3/proxy/apps/DefaultAppSupport.java | 400 ++++++++++++++++++ .../fdc3/proxy/channels/ChannelSelector.java | 44 ++ .../fdc3/proxy/channels/ChannelSupport.java | 95 +++++ .../fdc3/proxy/channels/DefaultChannel.java | 131 ++++++ .../proxy/channels/DefaultChannelSupport.java | 282 ++++++++++++ .../proxy/channels/DefaultPrivateChannel.java | 100 +++++ .../channels/UserChannelContextListener.java | 95 +++++ .../heartbeat/DefaultHeartbeatSupport.java | 129 ++++++ .../proxy/heartbeat/HeartbeatSupport.java | 28 ++ .../intents/DefaultIntentResolution.java | 72 ++++ .../proxy/intents/DefaultIntentSupport.java | 371 ++++++++++++++++ .../proxy/intents/IntentResolutionChoice.java | 42 ++ .../fdc3/proxy/intents/IntentResolver.java | 39 ++ .../fdc3/proxy/intents/IntentSupport.java | 80 ++++ .../listeners/DefaultContextListener.java | 136 ++++++ .../listeners/DefaultIntentListener.java | 133 ++++++ .../listeners/DesktopAgentEventListener.java | 101 +++++ .../PrivateChannelEventListener.java | 134 ++++++ .../proxy/listeners/RegisterableListener.java | 63 +++ .../proxy/messaging/AbstractMessaging.java | 166 ++++++++ .../com/finos/fdc3/proxy/util/Logger.java | 69 +++ .../proxy/CucumberSpringConfiguration.java | 42 ++ .../com/finos/fdc3/proxy/RunCucumberTest.java | 37 ++ .../proxy/steps/ChannelSelectorSteps.java | 80 ++++ .../finos/fdc3/proxy/steps/ChannelSteps.java | 333 +++++++++++++++ .../finos/fdc3/proxy/steps/GenericSteps.java | 81 ++++ .../finos/fdc3/proxy/steps/IntentSteps.java | 252 +++++++++++ .../com/finos/fdc3/proxy/steps/UtilSteps.java | 84 ++++ .../finos/fdc3/proxy/support/ContextMap.java | 66 +++ .../proxy/support/TestChannelSelector.java | 94 ++++ .../fdc3/proxy/support/TestMessaging.java | 217 ++++++++++ .../finos/fdc3/proxy/world/CustomWorld.java | 42 ++ .../resources/features/app-channels.feature | 116 +++++ .../resources/features/app-metadata.feature | 35 ++ .../test/resources/features/broadcast.feature | 32 ++ .../resources/features/find-intents.feature | 74 ++++ .../test/resources/features/heartbeat.feature | 18 + .../features/intent-listener.feature | 42 ++ .../resources/features/intent-results.feature | 128 ++++++ .../src/test/resources/features/open.feature | 52 +++ .../private-channels-deprecated.feature | 70 +++ .../features/private-channels.feature | 139 ++++++ .../resources/features/raise-intents.feature | 73 ++++ .../features/user-channel-selector.feature | 18 + .../features/user-channel-sync.feature | 45 ++ .../resources/features/user-channels.feature | 341 +++++++++++++++ .../src/test/resources/features/utils.feature | 10 + .../com/finos/fdc3/api/context/Context.java | 72 +++- .../fdc3/api/metadata/ContextMetadata.java | 14 +- .../fdc3/api/metadata/DisplayMetadata.java | 30 ++ .../api/metadata/ImplementationMetadata.java | 28 +- .../finos/fdc3/api/types/IntentResult.java | 8 +- pom.xml | 1 + 58 files changed, 5892 insertions(+), 35 deletions(-) create mode 100644 fdc3-agent-proxy/pom.xml create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java create mode 100644 fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java create mode 100644 fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java create mode 100644 fdc3-agent-proxy/src/test/resources/features/app-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/app-metadata.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/broadcast.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/find-intents.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/heartbeat.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/intent-listener.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/intent-results.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/open.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/private-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/raise-intents.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/features/utils.feature diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml new file mode 100644 index 00000000..5c930016 --- /dev/null +++ b/fdc3-agent-proxy/pom.xml @@ -0,0 +1,130 @@ + + + + 4.0.0 + + com.finos.fdc3.api + finos-bmo-hackathon + 1.0.0-SNAPSHOT + + + fdc3-agent-proxy + FDC3 Desktop Agent Proxy + Desktop Agent Proxy implementation for FDC3 + + + UTF-8 + 11 + 11 + 7.15.0 + 5.10.1 + 2.16.1 + + + + + + com.finos.fdc3.api + fdc3api + ${project.version} + + + + + com.finos.fdc3.api + fdc3-testing + ${project.version} + test + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + org.slf4j + slf4j-api + 2.0.9 + + + + + io.cucumber + cucumber-java + ${cucumber.version} + test + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + test + + + org.junit.platform + junit-platform-suite + 1.10.1 + test + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + com.jayway.jsonpath + json-path + 2.9.0 + test + + + + + + + src/test/resources + + + + + 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.plugin=pretty,html:target/cucumber-reports/cucumber.html + cucumber.glue=com.finos.fdc3.proxy.steps + cucumber.features=src/test/resources/features + + + + + + + + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java new file mode 100644 index 00000000..53e07449 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy; + +import java.util.concurrent.CompletionStage; + +/** + * Interface for objects that support connection lifecycle. + */ +public interface Connectable { + + /** + * Connect to the underlying resource. + * + * @return a CompletionStage that completes when connected + */ + CompletionStage connect(); + + /** + * Disconnect from the underlying resource. + * + * @return a CompletionStage that completes when disconnected + */ + CompletionStage disconnect(); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java new file mode 100644 index 00000000..332f8e41 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java @@ -0,0 +1,204 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.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 com.finos.fdc3.api.DesktopAgent; +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.channel.PrivateChannel; +import com.finos.fdc3.api.context.Context; +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.EventHandler; +import com.finos.fdc3.api.types.IntentHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.apps.AppSupport; +import com.finos.fdc3.proxy.channels.ChannelSupport; +import com.finos.fdc3.proxy.heartbeat.HeartbeatSupport; +import com.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 channels.getUserChannel() + .thenCompose(channel -> { + if (channel != null) { + return channel.broadcast(context); + } else { + return CompletableFuture.completedFuture(null); + } + }); + } + + @Override + public CompletionStage addContextListener(String contextType, ContextHandler handler) { + return channels.addContextListener(handler, contextType); + } + + @Override + public CompletionStage> getUserChannels() { + 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); + } + + @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 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 open(AppIdentifier app, Context context) { + return apps.open(app, 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/com/finos/fdc3/proxy/Messaging.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java new file mode 100644 index 00000000..0da0606e --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java @@ -0,0 +1,105 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.Predicate; + +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.proxy.listeners.RegisterableListener; + +/** + * 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 map + */ + Map 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(); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java new file mode 100644 index 00000000..f733ec5a --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java @@ -0,0 +1,64 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.apps; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.AppMetadata; +import com.finos.fdc3.api.metadata.ImplementationMetadata; +import com.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); + + /** + * 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/com/finos/fdc3/proxy/apps/DefaultAppSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java new file mode 100644 index 00000000..95fab50b --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java @@ -0,0 +1,400 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.apps; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.errors.OpenError; +import com.finos.fdc3.api.errors.ResolveError; +import com.finos.fdc3.api.metadata.AppMetadata; +import com.finos.fdc3.api.metadata.Icon; +import com.finos.fdc3.api.metadata.Image; +import com.finos.fdc3.api.metadata.ImplementationMetadata; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.util.Logger; + +/** + * 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 + @SuppressWarnings("unchecked") + public CompletionStage> findInstances(AppIdentifier app) { + Map request = new HashMap<>(); + request.put("type", "findInstancesRequest"); + request.put("meta", messaging.createMeta()); + + Map payload = new HashMap<>(); + Map appMap = new HashMap<>(); + appMap.put("appId", app.getAppId()); + app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); + payload.put("app", appMap); + request.put("payload", payload); + + return messaging.>exchange(request, "findInstancesResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + List> identifiers = (List>) responsePayload.get("appIdentifiers"); + + if (identifiers == null) { + return new ArrayList<>(); + } + + return identifiers.stream() + .map(id -> createAppIdentifier(id)) + .collect(Collectors.toList()); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage getAppMetadata(AppIdentifier app) { + Map request = new HashMap<>(); + request.put("type", "getAppMetadataRequest"); + request.put("meta", messaging.createMeta()); + + Map payload = new HashMap<>(); + Map appMap = new HashMap<>(); + appMap.put("appId", app.getAppId()); + app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); + payload.put("app", appMap); + request.put("payload", payload); + + return messaging.>exchange(request, "getAppMetadataResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map appMetadataMap = (Map) responsePayload.get("appMetadata"); + + if (appMetadataMap == null) { + throw new RuntimeException(ResolveError.TargetAppUnavailable.toString()); + } + + return parseAppMetadata(appMetadataMap); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage open(AppIdentifier app, Context context) { + Map request = new HashMap<>(); + request.put("type", "openRequest"); + request.put("meta", messaging.createMeta()); + + Map payload = new HashMap<>(); + Map appMap = new HashMap<>(); + appMap.put("appId", app.getAppId()); + app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); + payload.put("app", appMap); + if (context != null) { + payload.put("context", context.toMap()); + } + request.put("payload", payload); + + return messaging.>exchange(request, "openResponse", appLaunchTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map appIdentifierMap = (Map) responsePayload.get("appIdentifier"); + + if (appIdentifierMap == null) { + throw new RuntimeException(OpenError.AppNotFound.toString()); + } + + return createAppIdentifier(appIdentifierMap); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage getImplementationMetadata() { + Map request = new HashMap<>(); + request.put("type", "getInfoRequest"); + request.put("meta", messaging.createMeta()); + request.put("payload", new HashMap<>()); + + return messaging.>exchange(request, "getInfoResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map implMetadata = (Map) responsePayload.get("implementationMetadata"); + + if (implMetadata != null) { + return parseImplementationMetadata(implMetadata); + } else { + Logger.error("Invalid response from Desktop Agent to getInfo!"); + return createUnknownImplementationMetadata(); + } + }); + } + + private AppIdentifier createAppIdentifier(Map idMap) { + String appId = (String) idMap.get("appId"); + String instanceId = (String) idMap.get("instanceId"); + return new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + }; + } + + private AppMetadata parseAppMetadata(Map appMap) { + String appId = (String) appMap.get("appId"); + String instanceId = (String) appMap.get("instanceId"); + String name = (String) appMap.get("name"); + String title = (String) appMap.get("title"); + String description = (String) appMap.get("description"); + + return new AppMetadata() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + + @Override + public Optional getName() { + return Optional.ofNullable(name); + } + + @Override + public Optional getVersion() { + return Optional.empty(); + } + + @Override + public Optional> getInstanceMetadata() { + return Optional.empty(); + } + + @Override + public Optional getTitle() { + return Optional.ofNullable(title); + } + + @Override + public Optional getTooltip() { + return Optional.empty(); + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public Optional> getIcons() { + return Optional.empty(); + } + + @Override + public Optional> getScreenshots() { + return Optional.empty(); + } + + @Override + public Optional getResultType() { + return Optional.empty(); + } + }; + } + + @SuppressWarnings("unchecked") + private ImplementationMetadata parseImplementationMetadata(Map implMap) { + String fdc3Version = (String) implMap.get("fdc3Version"); + String provider = (String) implMap.get("provider"); + String providerVersion = (String) implMap.get("providerVersion"); + Map appMetadataMap = (Map) implMap.get("appMetadata"); + Map optionalFeaturesMap = (Map) implMap.get("optionalFeatures"); + + AppMetadata appMetadata = appMetadataMap != null ? parseAppMetadata(appMetadataMap) : null; + + return new ImplementationMetadata() { + @Override + public String getFdc3Version() { + return fdc3Version; + } + + @Override + public String getProvider() { + return provider; + } + + @Override + public String getProviderVersion() { + return providerVersion; + } + + @Override + public AppMetadata getAppMetadata() { + return appMetadata; + } + + @Override + public OptionalFeatures getOptionalFeatures() { + if (optionalFeaturesMap == null) { + return null; + } + return new OptionalFeatures() { + @Override + public boolean isOriginatingAppMetadata() { + Boolean val = (Boolean) optionalFeaturesMap.get("OriginatingAppMetadata"); + return val != null && val; + } + + @Override + public boolean isUserChannelMembershipAPIs() { + Boolean val = (Boolean) optionalFeaturesMap.get("UserChannelMembershipAPIs"); + return val != null && val; + } + + @Override + public boolean isDesktopAgentBridging() { + Boolean val = (Boolean) optionalFeaturesMap.get("DesktopAgentBridging"); + return val != null && val; + } + }; + } + }; + } + + private ImplementationMetadata createUnknownImplementationMetadata() { + return new ImplementationMetadata() { + @Override + public String getFdc3Version() { + return "unknown"; + } + + @Override + public String getProvider() { + return "unknown"; + } + + @Override + public String getProviderVersion() { + return null; + } + + @Override + public AppMetadata getAppMetadata() { + return new AppMetadata() { + @Override + public String getAppId() { + return "unknown"; + } + + @Override + public Optional getInstanceId() { + return Optional.of("unknown"); + } + + @Override + public Optional getName() { + return Optional.empty(); + } + + @Override + public Optional getVersion() { + return Optional.empty(); + } + + @Override + public Optional> getInstanceMetadata() { + return Optional.empty(); + } + + @Override + public Optional getTitle() { + return Optional.empty(); + } + + @Override + public Optional getTooltip() { + return Optional.empty(); + } + + @Override + public Optional getDescription() { + return Optional.empty(); + } + + @Override + public Optional> getIcons() { + return Optional.empty(); + } + + @Override + public Optional> getScreenshots() { + return Optional.empty(); + } + + @Override + public Optional getResultType() { + return Optional.empty(); + } + }; + } + + @Override + public OptionalFeatures getOptionalFeatures() { + return new OptionalFeatures() { + @Override + public boolean isOriginatingAppMetadata() { + return false; + } + + @Override + public boolean isUserChannelMembershipAPIs() { + return false; + } + + @Override + public boolean isDesktopAgentBridging() { + return false; + } + }; + } + }; + } +} diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java new file mode 100644 index 00000000..1c0f455d --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java @@ -0,0 +1,44 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +import java.util.List; +import java.util.function.Consumer; + +import com.finos.fdc3.api.channel.Channel; + +/** + * Interface for channel selection UI components. + */ +public interface ChannelSelector { + + /** + * Set the callback to be invoked when the user selects a different channel. + * + * @param callback the callback, receiving the channel ID or null if leaving + */ + void setChannelChangeCallback(Consumer callback); + + /** + * Update the current channel and available channels. + * + * @param channelId the current channel ID, or null if none + * @param channels the available channels + */ + void updateChannel(String channelId, List channels); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java new file mode 100644 index 00000000..dc861082 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java @@ -0,0 +1,95 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.channel.PrivateChannel; +import com.finos.fdc3.api.types.ContextHandler; +import com.finos.fdc3.api.types.EventHandler; +import com.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/com/finos/fdc3/proxy/channels/DefaultChannel.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java new file mode 100644 index 00000000..83a8df2c --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java @@ -0,0 +1,131 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +import java.util.HashMap; +import java.util.Map; +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 com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.DefaultContextListener; + +/** + * Default implementation of a Channel. + */ +public class DefaultChannel implements Channel { + + protected final Messaging messaging; + protected final long messageExchangeTimeout; + private final String id; + private final Type type; + private final DisplayMetadata displayMetadataValue; + + 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.displayMetadataValue = displayMetadata; + } + + @Override + public String getId() { + return id; + } + + @Override + public Type getType() { + return type; + } + + @Override + public Optional displayMetadata() { + return Optional.ofNullable(displayMetadataValue); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage broadcast(Context context) { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "broadcastRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", id); + payload.put("context", context.toMap()); + request.put("payload", payload); + + return messaging.>exchange(request, "broadcastResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + public CompletionStage> getCurrentContext() { + return getCurrentContext(null); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage> getCurrentContext(String contextType) { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "getCurrentContextRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", id); + payload.put("contextType", contextType); + request.put("payload", payload); + + return messaging.>exchange(request, "getCurrentContextResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map contextMap = (Map) responsePayload.get("context"); + if (contextMap != null) { + return Optional.of(Context.fromMap(contextMap)); + } + return Optional.empty(); + }); + } + + @Override + public CompletionStage addContextListener(String contextType, ContextHandler handler) { + return addContextListenerInner(contextType, handler); + } + + protected CompletionStage addContextListenerInner(String contextType, ContextHandler handler) { + DefaultContextListener listener = new DefaultContextListener( + messaging, + messageExchangeTimeout, + id, + contextType, + handler + ); + return listener.register().thenApply(v -> listener); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java new file mode 100644 index 00000000..2f30b7b1 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java @@ -0,0 +1,282 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +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.stream.Collectors; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.channel.PrivateChannel; +import com.finos.fdc3.api.errors.ChannelError; +import com.finos.fdc3.api.metadata.DisplayMetadata; +import com.finos.fdc3.api.types.ContextHandler; +import com.finos.fdc3.api.types.EventHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.DesktopAgentEventListener; +import com.finos.fdc3.proxy.util.Logger; + +/** + * Default implementation of ChannelSupport. + */ +public class DefaultChannelSupport implements ChannelSupport { + + private final Messaging messaging; + private final ChannelSelector channelSelector; + private final long messageExchangeTimeout; + private List userChannels = null; + private Channel currentChannel = null; + + 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); + } + }); + + // Listen for channel changed events from the Desktop Agent + 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); + + getUserChannelsCached().thenAccept(channels -> { + Channel theChannel = null; + if (newChannelId != null) { + theChannel = channels.stream() + .filter(c -> newChannelId.equals(c.getId())) + .findFirst() + .orElse(null); + + if (theChannel == null) { + Logger.debug("Unknown user channel, querying Desktop Agent: {}", newChannelId); + getUserChannels().thenAccept(updatedChannels -> { + Channel foundChannel = updatedChannels.stream() + .filter(c -> newChannelId.equals(c.getId())) + .findFirst() + .orElse(null); + if (foundChannel == null) { + Logger.warn("Received user channel update with unknown user channel: {}", newChannelId); + } + currentChannel = foundChannel; + channelSelector.updateChannel(newChannelId, updatedChannels); + }); + return; + } + } + currentChannel = theChannel; + channelSelector.updateChannel(theChannel != null ? theChannel.getId() : null, channels); + }); + }, "userChannelChanged"); + } + + @Override + public CompletionStage addEventListener(EventHandler handler, String type) { + DesktopAgentEventListener listener = new DesktopAgentEventListener( + messaging, messageExchangeTimeout, type, handler); + return listener.register().thenApply(v -> listener); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage getUserChannel() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "getCurrentChannelRequest"); + request.put("payload", new HashMap<>()); + + return messaging.>exchange(request, "getCurrentChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + Map payload = (Map) response.get("payload"); + Map channel = (Map) payload.get("channel"); + + if (channel == null) { + return null; + } + + String id = (String) channel.get("id"); + Map displayMetadataMap = (Map) channel.get("displayMetadata"); + DisplayMetadata displayMetadata = displayMetadataMap != null + ? DisplayMetadata.fromMap(displayMetadataMap) + : null; + + return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.User, displayMetadata); + }); + } + + private CompletionStage> getUserChannelsCached() { + if (userChannels != null) { + return CompletableFuture.completedFuture(userChannels); + } + return getUserChannels(); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage> getUserChannels() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "getUserChannelsRequest"); + request.put("payload", new HashMap<>()); + + return messaging.>exchange(request, "getUserChannelsResponse", messageExchangeTimeout) + .thenApply(response -> { + Map payload = (Map) response.get("payload"); + List> channelList = (List>) payload.get("userChannels"); + + if (channelList == null) { + userChannels = new ArrayList<>(); + return userChannels; + } + + userChannels = channelList.stream() + .map(c -> { + String id = (String) c.get("id"); + Map displayMetadataMap = (Map) c.get("displayMetadata"); + DisplayMetadata displayMetadata = displayMetadataMap != null + ? DisplayMetadata.fromMap(displayMetadataMap) + : null; + return (Channel) new DefaultChannel( + messaging, messageExchangeTimeout, id, Channel.Type.User, displayMetadata); + }) + .collect(Collectors.toList()); + + return userChannels; + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage getOrCreate(String id) { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "getOrCreateChannelRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", id); + request.put("payload", payload); + + return messaging.>exchange(request, "getOrCreateChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map channel = (Map) responsePayload.get("channel"); + + if (channel == null) { + throw new RuntimeException(ChannelError.CreationFailed.toString()); + } + + Map displayMetadataMap = (Map) channel.get("displayMetadata"); + DisplayMetadata displayMetadata = displayMetadataMap != null + ? DisplayMetadata.fromMap(displayMetadataMap) + : null; + + return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, displayMetadata); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage createPrivateChannel() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "createPrivateChannelRequest"); + request.put("payload", new HashMap<>()); + + return messaging.>exchange(request, "createPrivateChannelResponse", messageExchangeTimeout) + .thenApply(response -> { + Map payload = (Map) response.get("payload"); + Map channel = (Map) payload.get("privateChannel"); + + if (channel == null) { + throw new RuntimeException(ChannelError.CreationFailed.toString()); + } + + String id = (String) channel.get("id"); + return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage leaveUserChannel() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "leaveCurrentChannelRequest"); + request.put("payload", new HashMap<>()); + + return messaging.>exchange(request, "leaveCurrentChannelResponse", messageExchangeTimeout) + .thenCompose(response -> { + currentChannel = null; + return getUserChannelsCached().thenAccept(channels -> { + channelSelector.updateChannel(null, channels); + }); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage joinUserChannel(String id) { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "joinUserChannelRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", id); + request.put("payload", payload); + + return messaging.>exchange(request, "joinUserChannelResponse", messageExchangeTimeout) + .thenCompose(response -> getUserChannelsCached()) + .thenAccept(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); + }); + } + + @Override + public CompletionStage addContextListener(ContextHandler handler, String type) { + UserChannelContextListener listener = new UserChannelContextListener(this, messaging, messageExchangeTimeout, type, handler); + return listener.register().thenApply(v -> listener); + } + + // Package-private for UserChannelContextListener access + Channel getCurrentChannelInternal() { + return currentChannel; + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java new file mode 100644 index 00000000..7381e2b0 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java @@ -0,0 +1,100 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.channel.PrivateChannel; +import com.finos.fdc3.api.types.ContextHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.DefaultContextListener; +import com.finos.fdc3.proxy.listeners.PrivateChannelEventListener; + +/** + * 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 Listener onAddContextListener(Consumer> handler) { + PrivateChannelEventListener listener = new PrivateChannelEventListener( + messaging, messageExchangeTimeout, getId(), "addContextListener", + event -> handler.accept(Optional.ofNullable((String) event.getDetails()))); + listener.registerSync(); + return listener; + } + + @Override + public Listener onUnsubsrcibe(Consumer> handler) { + PrivateChannelEventListener listener = new PrivateChannelEventListener( + messaging, messageExchangeTimeout, getId(), "unsubscribe", + event -> handler.accept(Optional.ofNullable((String) event.getDetails()))); + listener.registerSync(); + return listener; + } + + @Override + public Listener onDisconnect(Runnable handler) { + PrivateChannelEventListener listener = new PrivateChannelEventListener( + messaging, messageExchangeTimeout, getId(), "disconnect", + event -> handler.run()); + listener.registerSync(); + return listener; + } + + @Override + @SuppressWarnings("unchecked") + public void disconnect() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "privateChannelDisconnectRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", getId()); + request.put("payload", payload); + + try { + messaging.>exchange(request, "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/com/finos/fdc3/proxy/channels/UserChannelContextListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java new file mode 100644 index 00000000..d844f844 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java @@ -0,0 +1,95 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.channels; + +import java.util.Map; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.types.ContextHandler; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.DefaultContextListener; + +/** + * Context listener that tracks user channel changes. + */ +public class UserChannelContextListener extends DefaultContextListener { + + private final DefaultChannelSupport channelSupport; + + public UserChannelContextListener( + 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. + */ + public CompletionStage changeChannel() { + Channel currentChannel = channelSupport.getCurrentChannelInternal(); + if (currentChannel != null) { + return currentChannel.getCurrentContext(contextType) + .thenAccept(contextOpt -> { + contextOpt.ifPresent(context -> handler.handleContext(context, null)); + }); + } + return java.util.concurrent.CompletableFuture.completedFuture(null); + } + + @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/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java new file mode 100644 index 00000000..0776fbcd --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java @@ -0,0 +1,129 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.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 java.util.concurrent.TimeUnit; + +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.RegisterableListener; +import com.finos.fdc3.proxy.util.Logger; + +/** + * 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 = (String) payload.get("timestamp"); + Logger.debug("Received heartbeat at {}", timestamp); + + // Respond to heartbeat + respondToHeartbeat(message); + } + + @Override + public CompletionStage register() { + messaging.register(this); + return CompletableFuture.completedFuture(null); + } + + @Override + public void unsubscribe() { + messaging.unregister(id); + } + }; + + messaging.register(heartbeatListener); + return CompletableFuture.completedFuture(null); + } + + @SuppressWarnings("unchecked") + private void respondToHeartbeat(Map heartbeatEvent) { + try { + Map meta = (Map) heartbeatEvent.get("meta"); + String requestUuid = (String) meta.get("requestUuid"); + + java.util.Map response = new java.util.HashMap<>(); + java.util.Map responseMeta = messaging.createMeta(); + responseMeta.put("requestUuid", requestUuid); + response.put("meta", responseMeta); + response.put("type", "heartbeatAcknowledgementRequest"); + + java.util.Map payload = new java.util.HashMap<>(); + payload.put("heartbeatEventUuid", requestUuid); + response.put("payload", payload); + + messaging.post(response); + } 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 CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java new file mode 100644 index 00000000..5dff343e --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java @@ -0,0 +1,28 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.heartbeat; + +import com.finos.fdc3.proxy.Connectable; + +/** + * 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/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java new file mode 100644 index 00000000..9f6949a7 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java @@ -0,0 +1,72 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.intents; + +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.metadata.IntentResolution; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.api.types.IntentResult; +import com.finos.fdc3.proxy.Messaging; + +/** + * Default implementation of IntentResolution. + */ +public class DefaultIntentResolution implements IntentResolution { + + private final Messaging messaging; + private final CompletionStage resultPromise; + private final AppIdentifier source; + private final String intent; + + public DefaultIntentResolution( + Messaging messaging, + CompletionStage resultPromise, + AppIdentifier source, + String intent) { + this.messaging = messaging; + this.resultPromise = resultPromise; + 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.thenApply(result -> new IntentResult() { + @Override + public Object getValue() { + return result; + } + }); + } +} diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java new file mode 100644 index 00000000..01713a95 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java @@ -0,0 +1,371 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.intents; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.errors.ResolveError; +import com.finos.fdc3.api.metadata.AppIntent; +import com.finos.fdc3.api.metadata.AppMetadata; +import com.finos.fdc3.api.metadata.Icon; +import com.finos.fdc3.api.metadata.Image; +import com.finos.fdc3.api.metadata.IntentMetadata; +import com.finos.fdc3.api.metadata.IntentResolution; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.api.types.IntentHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.DefaultIntentListener; + +/** + * 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 + @SuppressWarnings("unchecked") + public CompletionStage findIntent(String intent, Context context, String resultType) { + Map request = new HashMap<>(); + request.put("type", "findIntentRequest"); + request.put("meta", messaging.createMeta()); + + Map payload = new HashMap<>(); + payload.put("intent", intent); + if (context != null) { + payload.put("context", context.toMap()); + } + if (resultType != null) { + payload.put("resultType", resultType); + } + request.put("payload", payload); + + return messaging.>exchange(request, "findIntentResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + Map appIntentMap = (Map) responsePayload.get("appIntent"); + + if (appIntentMap == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + AppIntent appIntent = parseAppIntent(appIntentMap); + if (appIntent.getApps().isEmpty()) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + return appIntent; + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage> findIntentsByContext(Context context) { + Map request = new HashMap<>(); + request.put("type", "findIntentsByContextRequest"); + request.put("meta", messaging.createMeta()); + + Map payload = new HashMap<>(); + payload.put("context", context.toMap()); + request.put("payload", payload); + + return messaging.>exchange(request, "findIntentsByContextResponse", messageExchangeTimeout) + .thenApply(response -> { + Map responsePayload = (Map) response.get("payload"); + List> appIntentsList = (List>) responsePayload.get("appIntents"); + + if (appIntentsList == null || appIntentsList.isEmpty()) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + return appIntentsList.stream() + .map(this::parseAppIntent) + .collect(Collectors.toList()); + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage raiseIntent(String intent, Context context, AppIdentifier app) { + Map meta = messaging.createMeta(); + String requestUuid = (String) meta.get("requestUuid"); + + Map request = new HashMap<>(); + request.put("type", "raiseIntentRequest"); + request.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("intent", intent); + payload.put("context", context.toMap()); + if (app != null) { + Map appMap = new HashMap<>(); + appMap.put("appId", app.getAppId()); + app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); + payload.put("app", appMap); + } + request.put("payload", payload); + + CompletionStage resultPromise = createResultPromise(requestUuid); + + return messaging.>exchange(request, "raiseIntentResponse", appLaunchTimeout) + .thenCompose(response -> { + Map responsePayload = (Map) response.get("payload"); + Map appIntentMap = (Map) responsePayload.get("appIntent"); + Map intentResolutionMap = (Map) responsePayload.get("intentResolution"); + + if (appIntentMap == null && intentResolutionMap == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + if (appIntentMap != null) { + AppIntent appIntent = parseAppIntent(appIntentMap); + return intentResolver.chooseIntent(List.of(appIntent), context) + .thenCompose(choice -> { + if (choice == null) { + throw new RuntimeException(ResolveError.UserCancelled.toString()); + } + return raiseIntent(intent, context, choice.getAppId()); + }); + } else { + Map sourceMap = (Map) intentResolutionMap.get("source"); + String resolvedIntent = (String) intentResolutionMap.get("intent"); + AppIdentifier source = createAppIdentifier(sourceMap); + + return java.util.concurrent.CompletableFuture.completedFuture( + new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); + } + }); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage raiseIntentForContext(Context context, AppIdentifier app) { + Map meta = messaging.createMeta(); + String requestUuid = (String) meta.get("requestUuid"); + + Map request = new HashMap<>(); + request.put("type", "raiseIntentForContextRequest"); + request.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("context", context.toMap()); + if (app != null) { + Map appMap = new HashMap<>(); + appMap.put("appId", app.getAppId()); + app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); + payload.put("app", appMap); + } + request.put("payload", payload); + + CompletionStage resultPromise = createResultPromise(requestUuid); + + return messaging.>exchange(request, "raiseIntentForContextResponse", appLaunchTimeout) + .thenCompose(response -> { + Map responsePayload = (Map) response.get("payload"); + List> appIntentsList = (List>) responsePayload.get("appIntents"); + Map intentResolutionMap = (Map) responsePayload.get("intentResolution"); + + if ((appIntentsList == null || appIntentsList.isEmpty()) && intentResolutionMap == null) { + throw new RuntimeException(ResolveError.NoAppsFound.toString()); + } + + if (appIntentsList != null && !appIntentsList.isEmpty()) { + List appIntents = appIntentsList.stream() + .map(this::parseAppIntent) + .collect(Collectors.toList()); + + return intentResolver.chooseIntent(appIntents, context) + .thenCompose(choice -> { + if (choice == null) { + throw new RuntimeException(ResolveError.UserCancelled.toString()); + } + return raiseIntent(choice.getIntent(), context, choice.getAppId()); + }); + } else { + Map sourceMap = (Map) intentResolutionMap.get("source"); + String resolvedIntent = (String) intentResolutionMap.get("intent"); + AppIdentifier source = createAppIdentifier(sourceMap); + + return java.util.concurrent.CompletableFuture.completedFuture( + new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); + } + }); + } + + @Override + public CompletionStage addIntentListener(String intent, IntentHandler handler) { + DefaultIntentListener listener = new DefaultIntentListener(messaging, intent, handler, messageExchangeTimeout); + return listener.register().thenApply(v -> listener); + } + + @SuppressWarnings("unchecked") + private CompletionStage createResultPromise(String requestUuid) { + return 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 -> { + Map payload = (Map) response.get("payload"); + return payload.get("intentResult"); + }); + } + + private AppIdentifier createAppIdentifier(Map sourceMap) { + String appId = (String) sourceMap.get("appId"); + String instanceId = (String) sourceMap.get("instanceId"); + return new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + }; + } + + @SuppressWarnings("unchecked") + private AppIntent parseAppIntent(Map appIntentMap) { + Map intentMap = (Map) appIntentMap.get("intent"); + List> appsList = (List>) appIntentMap.get("apps"); + + String intentName = (String) intentMap.get("name"); + String displayName = (String) intentMap.get("displayName"); + + IntentMetadata intent = new IntentMetadata() { + @Override + public String getName() { + return intentName; + } + + @Override + public String getDisplayName() { + return displayName != null ? displayName : intentName; + } + }; + + List apps = appsList.stream() + .map(this::parseAppMetadata) + .collect(Collectors.toList()); + + return new AppIntent() { + @Override + public IntentMetadata getIntent() { + return intent; + } + + @Override + public List getApps() { + return apps; + } + }; + } + + private AppMetadata parseAppMetadata(Map appMap) { + String appId = (String) appMap.get("appId"); + String instanceId = (String) appMap.get("instanceId"); + String name = (String) appMap.get("name"); + String title = (String) appMap.get("title"); + String description = (String) appMap.get("description"); + + return new AppMetadata() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + + @Override + public Optional getName() { + return Optional.ofNullable(name); + } + + @Override + public Optional getVersion() { + return Optional.empty(); + } + + @Override + public Optional> getInstanceMetadata() { + return Optional.empty(); + } + + @Override + public Optional getTitle() { + return Optional.ofNullable(title); + } + + @Override + public Optional getTooltip() { + return Optional.empty(); + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public Optional> getIcons() { + return Optional.empty(); + } + + @Override + public Optional> getScreenshots() { + return Optional.empty(); + } + + @Override + public Optional getResultType() { + return Optional.empty(); + } + }; + } +} diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java new file mode 100644 index 00000000..d7af5560 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java @@ -0,0 +1,42 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.intents; + +import com.finos.fdc3.api.types.AppIdentifier; + +/** + * Represents the user's choice from an intent resolver. + */ +public class IntentResolutionChoice { + + private final String intent; + private final AppIdentifier appId; + + public IntentResolutionChoice(String intent, AppIdentifier appId) { + this.intent = intent; + this.appId = appId; + } + + public String getIntent() { + return intent; + } + + public AppIdentifier getAppId() { + return appId; + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java new file mode 100644 index 00000000..40692738 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java @@ -0,0 +1,39 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.intents; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.AppIntent; + +/** + * Interface for intent resolution UI components. + */ +public interface IntentResolver { + + /** + * Display a UI to let the user choose an intent and app. + * + * @param appIntents the available intents and apps + * @param context the context being passed + * @return a CompletionStage containing the user's choice, or null if cancelled + */ + CompletionStage chooseIntent(List appIntents, Context context); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java new file mode 100644 index 00000000..e8349818 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java @@ -0,0 +1,80 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.intents; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.AppIntent; +import com.finos.fdc3.api.metadata.IntentResolution; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.api.types.IntentHandler; +import com.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 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); + + /** + * 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/com/finos/fdc3/proxy/listeners/DefaultContextListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java new file mode 100644 index 00000000..efcc2d21 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java @@ -0,0 +1,136 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.types.ContextHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; + +/** + * Default implementation of a context listener. + */ +public class DefaultContextListener implements RegisterableListener, Listener { + + protected final Messaging messaging; + protected final long messageExchangeTimeout; + protected final String channelId; + protected final String contextType; + protected final ContextHandler handler; + protected final String messageType; + private final String id; + + 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) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.channelId = channelId; + this.contextType = contextType; + this.handler = handler; + this.messageType = messageType; + this.id = messaging.createUUID(); + } + + @Override + public String getId() { + return id; + } + + @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); + handler.handleContext(context, null); + } + + @Override + public CompletionStage register() { + // Register with messaging to receive broadcasts + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "addContextListenerRequest"); + + Map payload = new HashMap<>(); + payload.put("channelId", channelId); + payload.put("contextType", contextType); + request.put("payload", payload); + + messaging.register(this); + + return messaging.>exchange(request, "addContextListenerResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + public void unsubscribe() { + messaging.unregister(id); + } + + public CompletionStage unsubscribeAsync() { + messaging.unregister(id); + return CompletableFuture.completedFuture(null); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java new file mode 100644 index 00000000..a7f0c5a7 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java @@ -0,0 +1,133 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.ContextMetadata; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.api.types.IntentHandler; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; + +/** + * Default implementation of an intent listener. + */ +public class DefaultIntentListener implements RegisterableListener, Listener { + + private final Messaging messaging; + private final String intent; + private final IntentHandler handler; + private final long messageExchangeTimeout; + private final String id; + + public DefaultIntentListener( + Messaging messaging, + String intent, + IntentHandler handler, + long messageExchangeTimeout) { + this.messaging = messaging; + this.intent = intent; + this.handler = handler; + this.messageExchangeTimeout = messageExchangeTimeout; + this.id = messaging.createUUID(); + } + + @Override + public String getId() { + return id; + } + + @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) { + Map payload = (Map) message.get("payload"); + Map contextMap = (Map) payload.get("context"); + Context context = Context.fromMap(contextMap); + + // Create context metadata from the message + Map meta = (Map) message.get("meta"); + Map sourceMap = (Map) meta.get("source"); + + ContextMetadata contextMetadata = null; + if (sourceMap != null) { + String sourceAppId = (String) sourceMap.get("appId"); + String sourceInstanceId = (String) sourceMap.get("instanceId"); + contextMetadata = new ContextMetadata() { + @Override + public AppIdentifier getSource() { + return new AppIdentifier() { + @Override + public String getAppId() { + return sourceAppId; + } + + @Override + public java.util.Optional getInstanceId() { + return java.util.Optional.ofNullable(sourceInstanceId); + } + }; + } + }; + } + + handler.handleIntent(context, contextMetadata); + } + + @Override + public CompletionStage register() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "addIntentListenerRequest"); + + Map payload = new HashMap<>(); + payload.put("intent", intent); + request.put("payload", payload); + + messaging.register(this); + + return messaging.>exchange(request, "addIntentListenerResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + public void unsubscribe() { + messaging.unregister(id); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java new file mode 100644 index 00000000..56bd8715 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java @@ -0,0 +1,101 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.types.EventHandler; +import com.finos.fdc3.api.types.FDC3Event; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; + +/** + * Listener for Desktop Agent events. + */ +public class DesktopAgentEventListener implements RegisterableListener, Listener { + + private final Messaging messaging; + private final long messageExchangeTimeout; + private final String eventType; + private final EventHandler handler; + private final String id; + + public DesktopAgentEventListener( + Messaging messaging, + long messageExchangeTimeout, + String eventType, + EventHandler handler) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.eventType = eventType; + this.handler = handler; + this.id = messaging.createUUID(); + } + + @Override + public String getId() { + return id; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (eventType == null) { + // Listen to all events + return type != null && type.endsWith("Event"); + } + return getExpectedMessageType().equals(type); + } + + private String getExpectedMessageType() { + return eventType + "Event"; + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + Map payload = (Map) message.get("payload"); + FDC3Event> event = new FDC3Event<>(eventType, payload); + handler.handleEvent(event); + } + + @Override + public CompletionStage register() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "addEventListenerRequest"); + + Map payload = new HashMap<>(); + payload.put("type", eventType); + request.put("payload", payload); + + messaging.register(this); + + return messaging.>exchange(request, "addEventListenerResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + public void unsubscribe() { + messaging.unregister(id); + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java new file mode 100644 index 00000000..794e6e17 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java @@ -0,0 +1,134 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.listeners; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import com.finos.fdc3.api.types.EventHandler; +import com.finos.fdc3.api.types.FDC3Event; +import com.finos.fdc3.api.types.Listener; +import com.finos.fdc3.proxy.Messaging; + +/** + * Event listener for private channel events. + */ +public class PrivateChannelEventListener implements RegisterableListener, Listener { + + private final Messaging messaging; + private final long messageExchangeTimeout; + private final String channelId; + private final String eventType; + private final EventHandler handler; + private final String id; + + public PrivateChannelEventListener( + Messaging messaging, + long messageExchangeTimeout, + String channelId, + String eventType, + EventHandler handler) { + this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; + this.channelId = channelId; + this.eventType = eventType; + this.handler = handler; + this.id = messaging.createUUID(); + } + + @Override + public String getId() { + return id; + } + + @Override + @SuppressWarnings("unchecked") + public boolean filter(Map message) { + String type = (String) message.get("type"); + if (!getExpectedMessageType().equals(type)) { + return false; + } + + Map payload = (Map) message.get("payload"); + if (payload == null) { + return false; + } + + String msgChannelId = (String) payload.get("privateChannelId"); + return channelId.equals(msgChannelId); + } + + private String getExpectedMessageType() { + if (eventType == null) { + return "privateChannelOnEvent"; + } + switch (eventType) { + case "addContextListener": + return "privateChannelOnAddContextListenerEvent"; + case "unsubscribe": + return "privateChannelOnUnsubscribeEvent"; + case "disconnect": + return "privateChannelOnDisconnectEvent"; + default: + return "privateChannelOnEvent"; + } + } + + @Override + @SuppressWarnings("unchecked") + public void action(Map message) { + Map payload = (Map) message.get("payload"); + FDC3Event> event = new FDC3Event<>(eventType, payload); + handler.handleEvent(event); + } + + @Override + public CompletionStage register() { + Map request = new HashMap<>(); + request.put("meta", messaging.createMeta()); + request.put("type", "privateChannelAddEventListenerRequest"); + + Map payload = new HashMap<>(); + payload.put("privateChannelId", channelId); + payload.put("listenerType", eventType); + request.put("payload", payload); + + messaging.register(this); + + return messaging.>exchange(request, "privateChannelAddEventListenerResponse", messageExchangeTimeout) + .thenApply(response -> null); + } + + @Override + public void unsubscribe() { + messaging.unregister(id); + } + + /** + * Register synchronously. + */ + public void registerSync() { + try { + register().toCompletableFuture().get(); + } catch (Exception e) { + throw new RuntimeException("Failed to register listener", e); + } + } +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java new file mode 100644 index 00000000..5d2ffef7 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java @@ -0,0 +1,63 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.listeners; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * A listener that can be registered with the messaging system. + */ +public interface RegisterableListener { + + /** + * 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. + */ + void unsubscribe(); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java new file mode 100644 index 00000000..f206e9d9 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java @@ -0,0 +1,166 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.messaging; + +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 com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.proxy.Messaging; +import com.finos.fdc3.proxy.listeners.RegisterableListener; +import com.finos.fdc3.proxy.util.Logger; + +/** + * 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 final AppIdentifier appIdentifier; + + protected AbstractMessaging(AppIdentifier appIdentifier) { + this.appIdentifier = appIdentifier; + } + + @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 abstract Map createMeta(); + + @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 void unsubscribe() { + AbstractMessaging.this.unregister(id); + } + }; + + 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); + post(message); + + return 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 abstract CompletionStage disconnect(); +} + diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java new file mode 100644 index 00000000..db243429 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java @@ -0,0 +1,69 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.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/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java new file mode 100644 index 00000000..094ba204 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java @@ -0,0 +1,42 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy; + +import com.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.Before; +import io.cucumber.java.Scenario; + +/** + * Cucumber configuration and hooks. + */ +public class CucumberSpringConfiguration { + + private final CustomWorld world; + + public CucumberSpringConfiguration(CustomWorld world) { + this.world = world; + } + + @Before + public void beforeScenario(Scenario scenario) { + // Reset world state before each scenario + world.getProps().clear(); + world.setMessaging(null); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java new file mode 100644 index 00000000..29c2af96 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +/** + * Cucumber test runner for FDC3 Agent Proxy tests. + */ +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.finos.fdc3.proxy.steps,com.finos.fdc3.proxy.world,com.finos.fdc3.testing.steps") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty,html:target/cucumber-reports/cucumber.html") +public class RunCucumberTest { +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java new file mode 100644 index 00000000..4f418cfd --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java @@ -0,0 +1,80 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.steps; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.proxy.support.TestChannelSelector; +import com.finos.fdc3.proxy.support.TestMessaging; +import com.finos.fdc3.proxy.world.CustomWorld; +import com.finos.fdc3.testing.agent.SimpleIntentResolver; + +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) { + if (!world.hasMessaging()) { + @SuppressWarnings("unchecked") + Map> channelState = (Map>) world.get(ChannelSteps.CHANNEL_STATE); + world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>())); + } + + TestChannelSelector ts = new TestChannelSelector(); + world.set(selectorField, ts); + + // TODO: Create DefaultChannelSupport, DefaultHeartbeatSupport, etc. + // For now, we just set up the basic structure + + // Store the selector and desktop agent references + world.set(daField, new Object()); // Placeholder - replace with actual DesktopAgentProxy + world.set("result", null); + } + + @When("The first channel is selected via the channel selector in {string}") + public void theFirstChannelIsSelected(String selectorField) { + TestChannelSelector selector = (TestChannelSelector) world.get(selectorField); + selector.selectFirstChannel(); + } + + @When("The second channel is selected via the channel selector in {string}") + public void theSecondChannelIsSelected(String selectorField) { + TestChannelSelector selector = (TestChannelSelector) world.get(selectorField); + selector.selectSecondChannel(); + } + + @When("The channel is deselected via the channel selector in {string}") + public void theChannelIsDeselected(String selectorField) { + TestChannelSelector selector = (TestChannelSelector) world.get(selectorField); + selector.deselectChannel(); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java new file mode 100644 index 00000000..57537ff0 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java @@ -0,0 +1,333 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.steps; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.proxy.support.ContextMap; +import com.finos.fdc3.proxy.support.TestMessaging; +import com.finos.fdc3.proxy.world.CustomWorld; +import com.finos.fdc3.testing.agent.SimpleChannelSelector; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static com.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static com.finos.fdc3.testing.support.MatchingUtils.matchData; + +/** + * 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) { + 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)); + message.put("payload", payload); + + world.set(field, message); + } + + @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 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); + world.set(typeHandlerName, (Consumer) s -> types.add(s)); + } + + @Given("{string} pipes events to {string}") + public void pipesEventsTo(String typeHandlerName, String field) { + List events = new ArrayList<>(); + world.set(field, events); + world.set(typeHandlerName, (Consumer>) event -> { + if (event != null) { + events.add(event.get("details")); + } + }); + } + + @Given("{string} pipes context to {string}") + public void pipesContextTo(String contextHandlerName, String field) { + List contexts = new ArrayList<>(); + world.set(field, contexts); + world.set(contextHandlerName, (Consumer) contexts::add); + } + + @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); + // Store references to methods for later invocation + world.set("destructured_" + method1, new DestructuredMethod(object, method1)); + world.set("destructured_" + method2, new DestructuredMethod(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, new DestructuredMethod(object, methodName)); + } + + @When("I call destructured {string}") + public void iCallDestructured(String methodName) { + try { + DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); + Object result = dm.invoke(); + world.set("result", result); + } catch (Exception e) { + world.set("error", e); + world.set("result", null); + } + } + + @When("I call destructured {string} with parameter {string}") + public void iCallDestructuredWithParameter(String methodName, String param) { + try { + DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); + Object resolvedParam = handleResolve(param, world); + Object result = dm.invoke(resolvedParam); + world.set("result", result); + } catch (Exception e) { + world.set("error", e); + world.set("result", null); + } + } + + @When("I call destructured {string} with parameters {string} and {string}") + public void iCallDestructuredWithTwoParameters(String methodName, String param1, String param2) { + try { + DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); + Object resolvedParam1 = handleResolve(param1, world); + Object resolvedParam2 = handleResolve(param2, world); + Object result = dm.invoke(resolvedParam1, resolvedParam2); + world.set("result", result); + } catch (Exception e) { + world.set("error", e); + world.set("result", null); + } + } + + @When("I call destructured {string} with parameters {string} and {string} and {string}") + public void iCallDestructuredWithThreeParameters(String methodName, String param1, String param2, String param3) { + try { + DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); + Object resolvedParam1 = handleResolve(param1, world); + Object resolvedParam2 = handleResolve(param2, world); + Object resolvedParam3 = handleResolve(param3, world); + Object result = dm.invoke(resolvedParam1, resolvedParam2, resolvedParam3); + world.set("result", result); + } catch (Exception e) { + world.set("error", e); + world.set("result", null); + } + } + + /** + * 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 { + Class[] paramTypes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + paramTypes[i] = args[i] != null ? args[i].getClass() : Object.class; + } + + java.lang.reflect.Method method = findMethod(target.getClass(), methodName, args.length); + 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) { + return ((java.util.concurrent.CompletionStage) result).toCompletableFuture().get(); + } + return result; + } + + private java.lang.reflect.Method findMethod(Class clazz, String name, int paramCount) { + for (java.lang.reflect.Method m : clazz.getMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == paramCount) { + return m; + } + } + return null; + } + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java new file mode 100644 index 00000000..5e451f60 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java @@ -0,0 +1,81 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.steps; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.proxy.support.TestMessaging; +import com.finos.fdc3.proxy.world.CustomWorld; +import com.finos.fdc3.testing.agent.SimpleChannelSelector; +import com.finos.fdc3.testing.agent.SimpleIntentResolver; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.When; + +/** + * Generic Cucumber step definitions for agent-proxy tests. + */ +public class GenericSteps { + + private final CustomWorld world; + + public GenericSteps(CustomWorld world) { + this.world = world; + } + + @Given("A Desktop Agent in {string}") + public void aDesktopAgentIn(String field) { + if (!world.hasMessaging()) { + @SuppressWarnings("unchecked") + Map> channelState = (Map>) world.get(ChannelSteps.CHANNEL_STATE); + world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>())); + } + + // TODO: Create actual DesktopAgentProxy with: + // - DefaultChannelSupport + // - DefaultHeartbeatSupport + // - DefaultIntentSupport + // - DefaultAppSupport + + // For now, store a placeholder + world.set(field, new Object()); // Replace with actual DesktopAgentProxy + 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); + } + + @Given("schemas loaded") + public void schemasLoaded() { + // Schema loading would be configured externally + world.log("Schemas should be loaded by test configuration"); + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java new file mode 100644 index 00000000..05122b84 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java @@ -0,0 +1,252 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.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.function.BiConsumer; +import java.util.function.Supplier; + +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.metadata.ContextMetadata; +import com.finos.fdc3.api.types.AppIdentifier; +import com.finos.fdc3.proxy.support.TestMessaging; +import com.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.en.Given; + +import static com.finos.fdc3.testing.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 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() { + // Would need a Channel implementation + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + // intentResult.setChannel(...); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns a user channel") + public void raiseIntentReturnsUserChannel() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + // intentResult.setChannel(...); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("Raise Intent returns a private channel") + public void raiseIntentReturnsPrivateChannel() { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + // intentResult.setChannel(...); + 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"); + 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); + world.set(intentHandlerName, (BiConsumer) (context, metadata) -> { + Map item = new HashMap<>(); + item.put("context", context); + item.put("metadata", metadata); + intents.add(item); + }); + } + + @Given("{string} returns a context item") + public void returnsAContextItem(String intentHandlerName) { + world.set(intentHandlerName, (Supplier) () -> { + Map id = new HashMap<>(); + id.put("in", "one"); + id.put("out", "two"); + return new Context("fdc3.returned-intent", null, id); + }); + } + + @Given("{string} returns a channel") + public void returnsAChannel(String intentHandlerName) { + world.set(intentHandlerName, (Supplier>) () -> { + Map channel = new HashMap<>(); + channel.put("type", "private"); + channel.put("id", "some-channel-id"); + Map displayMetadata = new HashMap<>(); + displayMetadata.put("color", "ochre"); + displayMetadata.put("name", "Some Channel"); + channel.put("displayMetadata", displayMetadata); + return channel; + }); + } + + @Given("{string} returns a void promise") + public void returnsAVoidPromise(String intentHandlerName) { + world.set(intentHandlerName, (Supplier) () -> null); + } + + private AppIdentifier createAppIdentifier(String appId, String instanceId) { + return new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + }; + } + + 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; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java new file mode 100644 index 00000000..70b568d4 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java @@ -0,0 +1,84 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.steps; + +import com.finos.fdc3.proxy.world.CustomWorld; + +import io.cucumber.java.en.When; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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/com/finos/fdc3/proxy/support/ContextMap.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java new file mode 100644 index 00000000..8edf3f54 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java @@ -0,0 +1,66 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.support; + +import java.util.HashMap; +import java.util.Map; + +import com.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"); + 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/com/finos/fdc3/proxy/support/TestChannelSelector.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java new file mode 100644 index 00000000..fc5e5e5d --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java @@ -0,0 +1,94 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.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 com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.testing.agent.ChannelSelector; + +/** + * Test implementation of ChannelSelector for Cucumber tests. + */ +public class TestChannelSelector implements ChannelSelector { + + private Consumer callback; + private String channelId; + private List channels = new ArrayList<>(); + + @Override + public CompletionStage updateChannel(String channelId, List availableChannels) { + this.channelId = channelId; + this.channels = new ArrayList<>(availableChannels); + return CompletableFuture.completedFuture(null); + } + + @Override + public void setChannelChangeCallback(Consumer callback) { + this.callback = callback; + } + + @Override + public CompletionStage connect() { + System.out.println("TestChannelSelector was connected"); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + System.out.println("TestChannelSelector was disconnected"); + return CompletableFuture.completedFuture(null); + } + + public void selectChannel(String channelId) { + this.channelId = channelId; + if (callback != null) { + callback.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; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java new file mode 100644 index 00000000..cdbfaee0 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java @@ -0,0 +1,217 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.support; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import com.finos.fdc3.api.channel.Channel; +import com.finos.fdc3.api.context.Context; +import com.finos.fdc3.api.types.AppIdentifier; + +/** + * Test implementation of messaging for Cucumber tests. + * Simulates the message exchange between the Desktop Agent and apps. + */ +public class TestMessaging { + + private final List> allPosts = new ArrayList<>(); + private final List intentDetails = new ArrayList<>(); + private final Map> channelState; + private Channel currentChannel; + private PossibleIntentResult intentResult; + private final String appId; + private final String instanceId; + + public TestMessaging(Map> channelState) { + this.channelState = channelState != null ? channelState : new HashMap<>(); + this.appId = "cucumber-app"; + this.instanceId = "cucumber-instance"; + } + + public String createUUID() { + return UUID.randomUUID().toString(); + } + + public int getTimeoutMs() { + return 1000; + } + + public List> getAllPosts() { + return allPosts; + } + + public void post(Map message) { + allPosts.push(message); + } + + public void addAppIntentDetail(IntentDetail detail) { + intentDetails.add(detail); + } + + public List getIntentDetails() { + return intentDetails; + } + + public Map createMeta() { + Map meta = new HashMap<>(); + meta.put("requestUuid", createUUID()); + meta.put("timestamp", Instant.now().toString()); + Map source = new HashMap<>(); + source.put("appId", appId); + source.put("instanceId", instanceId); + meta.put("source", source); + return meta; + } + + public Map createResponseMeta() { + Map meta = createMeta(); + meta.put("responseUuid", createUUID()); + return meta; + } + + public Map createEventMeta() { + Map meta = createMeta(); + meta.put("eventUuid", createUUID()); + return meta; + } + + public void receive(Map message, Consumer log) { + // Process incoming messages + if (log != null) { + log.accept("Received message: " + message.get("type")); + } + } + + 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; + } + + public String getAppId() { + return appId; + } + + public String getInstanceId() { + return instanceId; + } + + /** + * 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 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 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/com/finos/fdc3/proxy/world/CustomWorld.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java new file mode 100644 index 00000000..a95ade94 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java @@ -0,0 +1,42 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 com.finos.fdc3.proxy.world; + +import com.finos.fdc3.proxy.support.TestMessaging; +import com.finos.fdc3.testing.world.PropsWorld; + +/** + * Custom Cucumber World for agent-proxy tests. + * Extends PropsWorld and adds messaging support. + */ +public class CustomWorld extends PropsWorld { + + private TestMessaging messaging; + + 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/features/app-channels.feature b/fdc3-agent-proxy/src/test/resources/features/app-channels.feature new file mode 100644 index 00000000..a3339d7b --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/app-channels.feature @@ -0,0 +1,116 @@ +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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameter "{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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "{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" with parameter "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" with parameters "{true}" and "{resultHandler}" + Then "{result}" is an error + And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{true}" + Then "{result}" is an error + + Scenario: Destructured channel methods - broadcast and addContextListener + When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "broadcast", "getCurrentContext" from "{channel1}" + And I call destructured "broadcast" with parameter "{instrumentContext}" + And I call destructured "getCurrentContext" with parameter "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" with parameters "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 | \ No newline at end of file diff --git a/fdc3-agent-proxy/src/test/resources/features/app-metadata.feature b/fdc3-agent-proxy/src/test/resources/features/app-metadata.feature new file mode 100644 index 00000000..1a5cdbef --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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" with parameter "{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" with parameter "{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/features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/features/broadcast.feature new file mode 100644 index 00000000..b0d9ac54 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/broadcast.feature @@ -0,0 +1,32 @@ +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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "broadcast" with parameter "{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" with parameter "{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" with parameter "one" + And I call "{api}" with "broadcast" with parameter "{instrumentContext}" + Then messaging will have posts + | payload.channelId | payload.context.type | payload.context.name | matches_type | + | one | {null} | {null} | joinUserChannelRequest | + | {null} | {null} | {null} | getUserChannelsRequest | + | {null} | {null} | {null} | getCurrentChannelRequest | + | one | fdc3.instrument | Apple | broadcastRequest | diff --git a/fdc3-agent-proxy/src/test/resources/features/find-intents.feature b/fdc3-agent-proxy/src/test/resources/features/find-intents.feature new file mode 100644 index 00000000..d7fc546b --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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" with parameter "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" with parameter "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" with parameters "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" with parameters "OrderFood" and "{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" with parameter "{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" with parameter "{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/features/heartbeat.feature b/fdc3-agent-proxy/src/test/resources/features/heartbeat.feature new file mode 100644 index 00000000..0123dd29 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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/features/intent-listener.feature b/fdc3-agent-proxy/src/test/resources/features/intent-listener.feature new file mode 100644 index 00000000..bcaa3bba --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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" with parameters "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 | some-app-id | + 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" with parameters "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" with parameters "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" with parameters "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/features/intent-results.feature b/fdc3-agent-proxy/src/test/resources/features/intent-results.feature new file mode 100644 index 00000000..83eae1db --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/intent-results.feature @@ -0,0 +1,128 @@ +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" with parameters "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" with parameters "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" with parameters "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" with parameters "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: Context Data is returned in the result + Given Raise Intent returns a context of "{instrumentContext}" + When I call "{api}" with "raiseIntent" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/open.feature b/fdc3-agent-proxy/src/test/resources/features/open.feature new file mode 100644 index 00000000..dc588092 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/open.feature @@ -0,0 +1,52 @@ +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" with parameters "{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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature b/fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature new file mode 100644 index 00000000..d03f459d --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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/features/private-channels.feature b/fdc3-agent-proxy/src/test/resources/features/private-channels.feature new file mode 100644 index 00000000..e498f21b --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/private-channels.feature @@ -0,0 +1,139 @@ +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" with parameters "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" with parameters "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: 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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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" with parameters "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" with parameter "{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" with parameters "fdc3.instrument" and "{resultHandler}" + And I call destructured "broadcast" with parameter "{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/features/raise-intents.feature b/fdc3-agent-proxy/src/test/resources/features/raise-intents.feature new file mode 100644 index 00000000..952af5b0 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/raise-intents.feature @@ -0,0 +1,73 @@ +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" with parameters "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" with parameters "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" with parameters "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" with parameter "{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" with parameters "{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" with parameter "{cancelContext}" + Then "{result}" is an error with message "UserCancelledResolution" + And messaging will have posts + | payload.context.type | matches_type | + | fdc3.cancel-me | raiseIntentForContextRequest | diff --git a/fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature b/fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature new file mode 100644 index 00000000..cecbb46f --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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/features/user-channel-sync.feature b/fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature new file mode 100644 index 00000000..298a7696 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature @@ -0,0 +1,45 @@ +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" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And we wait for a period of "600" 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" with parameter "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 | + | {null} | {null} | {null} | getCurrentChannelRequest | + | 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" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "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/features/user-channels.feature b/fdc3-agent-proxy/src/test/resources/features/user-channels.feature new file mode 100644 index 00000000..fdfadcd6 --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/features/user-channels.feature @@ -0,0 +1,341 @@ +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" with parameter "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 | + | {null} | getCurrentChannelRequest | + + Scenario: Changing Channel via Deprecated API + You should be able to join a channel knowing it's ID. + + When I call "{api}" with "joinChannel" with parameter "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 | + | {null} | getCurrentChannelRequest | + + Scenario: Adding a Typed Listener on a given User Channel + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "{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" with parameter "one" + And I call "{api}" with "addContextListener" with parameter "{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" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "{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" with parameters "{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" with parameter "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And I call "{api}" with "broadcast" with parameter "{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 | + | {null} | {null} | {null} | getCurrentChannelRequest | + | {null} | {null} | {null} | getCurrentChannelRequest | + | 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" with parameter "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And messaging receives "{instrumentMessageOne}" + And I call "{theChannel}" with "getCurrentContext" with parameter "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "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" with parameters "{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" with parameters "unknownEventType" and "{typesHandler}" + Then "{result}" is an error with message "UnknownEventType" + + 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 | + And messaging will have posts + | meta.source.appId | meta.source.instanceId | matches_type | + | cucumber-app | cucumber-instance | getUserChannelsRequest | + + Scenario: Destructured joinUserChannel and getCurrentChannel work correctly + When I destructure method "joinUserChannel" from "{api}" + And I call destructured "joinUserChannel" with parameter "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 | + | {null} | getCurrentChannelRequest | + + Scenario: Destructured channel getCurrentContext after broadcast + Given "resultHandler" pipes context to "contexts" + When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "{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" with parameter "one" + And I call destructured "broadcast" with parameter "{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" with parameter "one" + And I call destructured "addContextListener" with parameters "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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/utils.feature b/fdc3-agent-proxy/src/test/resources/features/utils.feature new file mode 100644 index 00000000..6298470e --- /dev/null +++ b/fdc3-agent-proxy/src/test/resources/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/fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java b/fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java index c4a48479..9d087b02 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.finos.fdc3.api.utils.StringUtilities; +import java.util.HashMap; import java.util.Map; @JsonPropertyOrder({ @@ -27,8 +28,7 @@ "name", "id" }) -public class Context -{ +public class Context { @JsonProperty("type") private String type; @@ -38,7 +38,9 @@ public class Context @JsonProperty("id") private Map id; - public Context() {} + public Context() { + } + public Context(String type) { this.type = type; this.name = null; @@ -63,45 +65,69 @@ public Context(String type, String name, Map id) { this.setId(id); } - public String getType() - { + public String getType() { return type; } - public Map getId() - { + public Map getId() { return id; } - public void setId(Map id) - { + public void setId(Map id) { /* - Map keyMap = ContextContants.CONTEXT_ID_KEYS_MAPPING.getOrDefault(type, null); - if(!ContextHelper.hasValidKeys(id, keyMap)) { - throw new IllegalArgumentException("Invalid ID(s) present!"); - } - if(!ContextHelper.hasMandatoryKeys(id, keyMap)) { - throw new IllegalArgumentException("Mandatory ID(s) missing!"); - } - */ + * Map keyMap = + * ContextContants.CONTEXT_ID_KEYS_MAPPING.getOrDefault(type, null); + * if(!ContextHelper.hasValidKeys(id, keyMap)) { + * throw new IllegalArgumentException("Invalid ID(s) present!"); + * } + * if(!ContextHelper.hasMandatoryKeys(id, keyMap)) { + * throw new IllegalArgumentException("Mandatory ID(s) missing!"); + * } + */ this.id = id; } - public String getName() - { + public String getName() { return name; } - public void setName(String name) - { + public void setName(String name) { this.name = name; } @Override - public String toString() - { + public String toString() { return StringUtilities.valueAsString(this); } + /** + * Convert this Context to a Map for JSON serialization. + */ + public Map toMap() { + Map map = new HashMap<>(); + map.put("type", type); + if (name != null) { + map.put("name", name); + } + if (id != null) { + map.put("id", id); + } + return map; + } + + /** + * Create a Context from a Map. + */ + @SuppressWarnings("unchecked") + public static Context fromMap(Map map) { + if (map == null) { + return null; + } + String type = (String) map.get("type"); + String name = (String) map.get("name"); + Map id = (Map) map.get("id"); + return new Context(type, name, id); + } + } diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java index 9ddc2f4f..a6405ed8 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java @@ -29,5 +29,17 @@ public interface ContextMetadata { * * @experimental */ - public AppIdentifier getSource(); + AppIdentifier getSource(); + + /** Get the source app ID. Convenience method. */ + default String getSourceAppId() { + AppIdentifier source = getSource(); + return source != null ? source.getAppId() : null; + } + + /** Get the source instance ID. Convenience method. */ + default String getSourceInstanceId() { + AppIdentifier source = getSource(); + return source != null ? source.getInstanceId() : null; + } } diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java index d40aa886..834398f1 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java @@ -18,6 +18,8 @@ import java.util.Optional; +import java.util.Map; + /** * A system channel will be global enough to have a presence across many apps. This gives us some hints * to render them in a standard way. It is assumed it may have other properties too, but if it has these, @@ -38,4 +40,32 @@ public interface DisplayMetadata { * A URL of an image that can be used to display this channel */ public Optional getGlyph(); + + /** + * Create a DisplayMetadata from a Map. + */ + public static DisplayMetadata fromMap(Map map) { + if (map == null) { + return null; + } + String name = (String) map.get("name"); + String color = (String) map.get("color"); + String glyph = (String) map.get("glyph"); + return new DisplayMetadata() { + @Override + public Optional getName() { + return Optional.ofNullable(name); + } + + @Override + public Optional getColor() { + return Optional.ofNullable(color); + } + + @Override + public Optional getGlyph() { + return Optional.ofNullable(glyph); + } + }; + } } diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java index e3490407..61ac81b7 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java @@ -25,28 +25,36 @@ public interface ImplementationMetadata { /** The version number of the FDC3 specification that the implementation provides. * The string must be a numeric semver version, e.g. 1.2 or 1.2.1. */ - public String getfdc3Version(); + String getFdc3Version(); /** The name of the provider of the Desktop Agent implementation (e.g. Finsemble, Glue42, OpenFin etc.). */ - public String getProvider(); + String getProvider(); /** The version of the provider of the Desktop Agent implementation (e.g. 5.3.0). */ - public Optional getProviderVersion(); + String getProviderVersion(); + + /** The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`). */ + AppMetadata getAppMetadata(); /** Metadata indicating whether the Desktop Agent implements optional features of * the Desktop Agent API. */ - public interface optionalFeatures { - /** Used to indicate whether the exposure of 'origninating app metadata' for + OptionalFeatures getOptionalFeatures(); + + /** Metadata indicating whether the Desktop Agent implements optional features of + * the Desktop Agent API. + */ + interface OptionalFeatures { + /** Used to indicate whether the exposure of 'originating app metadata' for * context and intent messages is supported by the Desktop Agent.*/ - public boolean OriginatingAppMetadata(); + boolean isOriginatingAppMetadata(); /** Used to indicate whether the optional `fdc3.joinUserChannel`, * `fdc3.getCurrentChannel` and `fdc3.leaveCurrentChannel` are implemented by * the Desktop Agent.*/ - public boolean UserChannelMembershipAPIs(); - } + boolean isUserChannelMembershipAPIs(); - /** The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`). */ - public AppMetadata getAppMetaData(); + /** Used to indicate whether Desktop Agent bridging is supported by the Desktop Agent. */ + boolean isDesktopAgentBridging(); + } } diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java b/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java index 397def66..fcd7806e 100644 --- a/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java +++ b/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java @@ -25,5 +25,11 @@ * implemented by {@link Context} and {@link Channel} */ public interface IntentResult { - + /** + * Get the underlying value of this result. + * This may be a Context, Channel, or null. + */ + default Object getValue() { + return this; + } } diff --git a/pom.xml b/pom.xml index a26eea76..495fcd48 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ fdc3api fdc3-testing + fdc3-agent-proxy 11 From 0ae8c040f050b1dafedf973eac5fa3315aed8fb3 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 10 Dec 2025 13:46:18 +0000 Subject: [PATCH 04/65] Generating java DTOs in fdc3-context and fdc3-schema --- fdc3-agent-proxy/pom.xml | 13 +- .../finos/fdc3/proxy/steps/GenericSteps.java | 5 - .../fdc3/proxy/support/TestMessaging.java | 2 +- fdc3-context/pom.xml | 181 ++++++++++++++++++ fdc3-schema/pom.xml | 181 ++++++++++++++++++ pom.xml | 2 + 6 files changed, 377 insertions(+), 7 deletions(-) create mode 100644 fdc3-context/pom.xml create mode 100644 fdc3-schema/pom.xml diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index 5c930016..97bd6920 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -45,6 +45,11 @@ jackson-core ${jackson.version} + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + com.fasterxml.jackson.core jackson-databind @@ -65,6 +70,12 @@ ${cucumber.version} test + + io.cucumber + cucumber-picocontainer + ${cucumber.version} + test + io.cucumber cucumber-junit-platform-engine @@ -118,7 +129,7 @@ cucumber.junit-platform.naming-strategy=long cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html - cucumber.glue=com.finos.fdc3.proxy.steps + cucumber.glue=com.finos.fdc3.proxy.steps,com.finos.fdc3.proxy.world,com.finos.fdc3.testing.steps cucumber.features=src/test/resources/features diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java index 5e451f60..0cd60085 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java @@ -72,10 +72,5 @@ public void messagingReceivesHeartbeatEvent() { world.getMessaging().receive(message, null); } - @Given("schemas loaded") - public void schemasLoaded() { - // Schema loading would be configured externally - world.log("Schemas should be loaded by test configuration"); - } } diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java index cdbfaee0..f1cc6bd9 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java +++ b/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java @@ -61,7 +61,7 @@ public List> getAllPosts() { } public void post(Map message) { - allPosts.push(message); + allPosts.add(message); } public void addAppIntentDetail(IntentDetail detail) { diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml new file mode 100644 index 00000000..7f3e198c --- /dev/null +++ b/fdc3-context/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + + com.finos.fdc3.api + finos-bmo-hackathon + 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.0 + + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + com.fasterxml.jackson.core + jackson-databind + 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-antrun-plugin + 3.1.0 + + + create-directories + initialize + + 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 + + + install @finos/fdc3-context@${fdc3.context.version} quicktype --save + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.1 + + + + quicktype-context + generate-sources + + exec + + + ${project.build.directory}/node-installation/node/node + ${project.build.directory}/npm-work + + node_modules/.bin/quicktype + --src-lang + schema + --lang + java + --package + com.finos.fdc3.context + --out + ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java + node_modules/@finos/fdc3-context/dist/schemas/context + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/fdc3 + + + + + + + + diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml new file mode 100644 index 00000000..a9c00e15 --- /dev/null +++ b/fdc3-schema/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + + com.finos.fdc3.api + finos-bmo-hackathon + 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.0 + + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + com.fasterxml.jackson.core + jackson-databind + 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-antrun-plugin + 3.1.0 + + + create-directories + initialize + + 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 + + + install @finos/fdc3-schema@${fdc3.schema.version} quicktype --save + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.1 + + + + quicktype-api + generate-sources + + exec + + + ${project.build.directory}/node-installation/node/node + ${project.build.directory}/npm-work + + node_modules/.bin/quicktype + --src-lang + schema + --lang + java + --package + com.finos.fdc3.schema.api + --out + ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java + node_modules/@finos/fdc3-schema/dist/schemas/api + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/fdc3 + + + + + + + + diff --git a/pom.xml b/pom.xml index 495fcd48..8c701719 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,8 @@ finos-bmo-hackathon http://maven.apache.org + fdc3-schema + fdc3-context fdc3api fdc3-testing fdc3-agent-proxy From a1e8c966237b4ac3c1b40012841d1d0e6972e711 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 13:40:37 +0000 Subject: [PATCH 05/65] Fixed type generation --- fdc3-context/pom.xml | 19 +++++-------------- fdc3-schema/pom.xml | 37 +++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index 7f3e198c..c0961e6a 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -117,19 +117,18 @@ npm - install @finos/fdc3-context@${fdc3.context.version} quicktype --save + install @finos/fdc3-context@${fdc3.context.version} @finos/fdc3-schema@${fdc3.context.version} quicktype --save - + org.codehaus.mojo exec-maven-plugin 3.1.1 - quicktype-context generate-sources @@ -137,19 +136,11 @@ exec - ${project.build.directory}/node-installation/node/node + /bin/sh ${project.build.directory}/npm-work - node_modules/.bin/quicktype - --src-lang - schema - --lang - java - --package - com.finos.fdc3.context - --out - ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java - node_modules/@finos/fdc3-context/dist/schemas/context + -c + node_modules/.bin/quicktype -s schema --lang java --package com.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index a9c00e15..c1a4b60b 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -109,7 +109,7 @@ init -y - + npm-install-packages generate-sources @@ -117,7 +117,7 @@ npm - install @finos/fdc3-schema@${fdc3.schema.version} quicktype --save + install @finos/fdc3-schema@${fdc3.schema.version} @finos/fdc3-context@${fdc3.schema.version} quicktype --save @@ -129,7 +129,24 @@ exec-maven-plugin 3.1.1 - + + + create-context-symlink + generate-sources + + exec + + + ln + ${project.build.directory}/npm-work + + -sfn + ../../@finos/fdc3-context/dist/schemas/context + node_modules/@finos/fdc3-schema/dist/schemas/context + + + + quicktype-api generate-sources @@ -137,19 +154,11 @@ exec - ${project.build.directory}/node-installation/node/node + /bin/sh ${project.build.directory}/npm-work - node_modules/.bin/quicktype - --src-lang - schema - --lang - java - --package - com.finos.fdc3.schema.api - --out - ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java - node_modules/@finos/fdc3-schema/dist/schemas/api + -c + node_modules/.bin/quicktype -s schema --lang java --package com.finos.fdc3.schema.api --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) From c4e840f6023506a31113117dc3ec553b0ddf3369 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 13:49:54 +0000 Subject: [PATCH 06/65] quiet the output --- fdc3-context/pom.xml | 2 +- fdc3-schema/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index c0961e6a..841bd3c1 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -140,7 +140,7 @@ ${project.build.directory}/npm-work -c - node_modules/.bin/quicktype -s schema --lang java --package com.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) + node_modules/.bin/quicktype -s schema --quiet --lang java --package com.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index c1a4b60b..03fa2aa2 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -158,7 +158,7 @@ ${project.build.directory}/npm-work -c - node_modules/.bin/quicktype -s schema --lang java --package com.finos.fdc3.schema.api --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) + node_modules/.bin/quicktype -s schema --quiet --lang java --package com.finos.fdc3.schema.api --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) From 34e5734506ef686ecdb0906d45aaa537e785b41f Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 13:59:18 +0000 Subject: [PATCH 07/65] moving packages / names --- fdc3-agent-proxy/pom.xml | 10 +++--- fdc3-context/pom.xml | 6 ++-- fdc3-schema/pom.xml | 4 +-- {fdc3api => fdc3-standard}/pom.xml | 8 ++--- .../org}/finos/fdc3/api/DesktopAgent.java | 0 .../org}/finos/fdc3/api/channel/Channel.java | 0 .../fdc3/api/channel/PrivateChannel.java | 0 .../org}/finos/fdc3/api/context/Chart.java | 0 .../fdc3/api/context/ChatInitSettings.java | 0 .../org}/finos/fdc3/api/context/Contact.java | 0 .../finos/fdc3/api/context/ContactList.java | 0 .../org}/finos/fdc3/api/context/Context.java | 0 .../fdc3/api/context/ContextContants.java | 0 .../finos/fdc3/api/context/ContextHelper.java | 0 .../org}/finos/fdc3/api/context/Country.java | 0 .../org}/finos/fdc3/api/context/Currency.java | 0 .../org}/finos/fdc3/api/context/Email.java | 0 .../finos/fdc3/api/context/Instrument.java | 0 .../fdc3/api/context/InstrumentList.java | 0 .../org}/finos/fdc3/api/context/Nothing.java | 0 .../org}/finos/fdc3/api/context/Options.java | 0 .../finos/fdc3/api/context/Organization.java | 0 .../finos/fdc3/api/context/Portfolio.java | 0 .../org}/finos/fdc3/api/context/Position.java | 0 .../finos/fdc3/api/context/TimeRange.java | 0 .../finos/fdc3/api/context/Valuation.java | 0 .../finos/fdc3/api/errors/ChannelError.java | 0 .../api/errors/FDC3ConnectionException.java | 0 .../org}/finos/fdc3/api/errors/OpenError.java | 0 .../finos/fdc3/api/errors/ResolveError.java | 0 .../finos/fdc3/api/errors/ResultError.java | 0 .../finos/fdc3/api/metadata/AppIntent.java | 0 .../finos/fdc3/api/metadata/AppMetadata.java | 0 .../fdc3/api/metadata/ContextMetadata.java | 0 .../fdc3/api/metadata/DisplayMetadata.java | 0 .../org}/finos/fdc3/api/metadata/Icon.java | 0 .../org}/finos/fdc3/api/metadata/Image.java | 0 .../api/metadata/ImplementationMetadata.java | 0 .../fdc3/api/metadata/IntentMetadata.java | 0 .../fdc3/api/metadata/IntentResolution.java | 0 .../finos/fdc3/api/types/AppIdentifier.java | 0 .../finos/fdc3/api/types/ContextHandler.java | 0 .../finos/fdc3/api/types/EventHandler.java | 0 .../org}/finos/fdc3/api/types/FDC3Event.java | 0 .../finos/fdc3/api/types/IntentHandler.java | 0 .../finos/fdc3/api/types/IntentResult.java | 0 .../org}/finos/fdc3/api/types/Listener.java | 0 .../fdc3/api/utils/JacksonUtilities.java | 0 .../finos/fdc3/api/utils/StringUtilities.java | 0 fdc3-testing/pom.xml | 8 ++--- pom.xml | 8 ++--- rename-directories.sh | 30 ++++++++++++++++++ rename-packages.sh | 22 +++++++++++++ {client => unused/client}/pom.xml | 0 .../finos/fdc3/client/AppGUIException.java | 0 .../java/com/finos/fdc3/client/AppUI.java | 0 .../finos/fdc3/client/data/IncomingData.java | 0 .../com/finos/fdc3/client/data/OrderData.java | 0 .../com/finos/fdc3/client/data/SnPData.java | 0 .../fdc3/client/handler/DataLoadHandler.java | 0 .../client/handler/SystemChannelHandler.java | 0 .../launcher/FDC3JavaAPIDemoApplication.java | 0 .../client/model/IncomingDataTableModel.java | 0 .../fdc3/client/model/OrderTableModel.java | 0 .../fdc3/client/model/SnPTableModel.java | 0 .../publisher/OrderMessagePublisher.java | 0 .../fdc3/client/utils/JSONAdapterUtil.java | 0 .../client/utils/SampleDataLoaderUtil.java | 0 .../fdc3/client/utils/StringUtilities.java | 0 .../finos/fdc3/client/utils/SwingUtils.java | 0 .../fdc3container}/fdc3-platform.json | 0 .../fdc3container}/package.json | 0 .../fdc3container}/receiver.html | 0 .../fdc3container}/receiver.json | 0 .../fdc3container}/receiver/receiver.js | 0 .../fdc3container}/receiver/style.css | 0 .../fdc3container}/sender.html | 0 .../fdc3container}/sender.json | 0 .../fdc3container}/server.js | 0 .../package-lock.json | 0 .../fdcreceiver-react-trade-app}/package.json | 0 .../public/favicon.ico | Bin .../public/index.html | 0 .../public/logo192.png | Bin .../public/logo512.png | Bin .../public/manifest.json | 0 .../public/robots.txt | 0 .../fdcreceiver-react-trade-app}/src/App.js | 0 .../src/components/Card.js | 0 .../src/components/ChannelDropdown.js | 0 .../src/components/Scroll.js | 0 .../src/components/Search.js | 0 .../src/components/SearchList.js | 0 .../src/data/initialDetails.js | 0 .../fdcreceiver-react-trade-app}/src/index.js | 0 .../openfin-fdc3-adapter}/pom.xml | 0 .../fdc3/adapter/openfin/OpenFinChannel.java | 0 .../OpenFinChannelDisplayMetadata.java | 0 .../OpenFinContextListenerAdapter.java | 0 .../adapter/openfin/OpenFinDesktopAgent.java | 0 .../adapter/openfin/OpenFinFDC3Config.java | 0 .../openfin/OpenFinIntentListenerAdapter.java | 0 102 files changed, 74 insertions(+), 22 deletions(-) rename {fdc3api => fdc3-standard}/pom.xml (94%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/DesktopAgent.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/channel/Channel.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/channel/PrivateChannel.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Chart.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/ChatInitSettings.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Contact.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/ContactList.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Context.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/ContextContants.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/ContextHelper.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Country.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Currency.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Email.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Instrument.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/InstrumentList.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Nothing.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Options.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Organization.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Portfolio.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Position.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/TimeRange.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/context/Valuation.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/errors/ChannelError.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/errors/FDC3ConnectionException.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/errors/OpenError.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/errors/ResolveError.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/errors/ResultError.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/AppIntent.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/AppMetadata.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/ContextMetadata.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/DisplayMetadata.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/Icon.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/Image.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/ImplementationMetadata.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/IntentMetadata.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/metadata/IntentResolution.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/AppIdentifier.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/ContextHandler.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/EventHandler.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/FDC3Event.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/IntentHandler.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/IntentResult.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/types/Listener.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/utils/JacksonUtilities.java (100%) rename {fdc3api/src/main/java/com => fdc3-standard/src/main/java/org}/finos/fdc3/api/utils/StringUtilities.java (100%) create mode 100755 rename-directories.sh create mode 100755 rename-packages.sh rename {client => unused/client}/pom.xml (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/AppGUIException.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/AppUI.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/data/IncomingData.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/data/OrderData.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/data/SnPData.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java (100%) rename {client => unused/client}/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java (100%) rename {fdc3container => unused/fdc3container}/fdc3-platform.json (100%) rename {fdc3container => unused/fdc3container}/package.json (100%) rename {fdc3container => unused/fdc3container}/receiver.html (100%) rename {fdc3container => unused/fdc3container}/receiver.json (100%) rename {fdc3container => unused/fdc3container}/receiver/receiver.js (100%) rename {fdc3container => unused/fdc3container}/receiver/style.css (100%) rename {fdc3container => unused/fdc3container}/sender.html (100%) rename {fdc3container => unused/fdc3container}/sender.json (100%) rename {fdc3container => unused/fdc3container}/server.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/package-lock.json (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/package.json (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/favicon.ico (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/index.html (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/logo192.png (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/logo512.png (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/manifest.json (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/public/robots.txt (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/App.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/components/Card.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/components/ChannelDropdown.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/components/Scroll.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/components/Search.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/components/SearchList.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/data/initialDetails.js (100%) rename {fdcreceiver-react-trade-app => unused/fdcreceiver-react-trade-app}/src/index.js (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/pom.xml (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java (100%) rename {openfin-fdc3-adapter => unused/openfin-fdc3-adapter}/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java (100%) diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index 97bd6920..aabc3f59 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -5,8 +5,8 @@ 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 - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT @@ -26,14 +26,14 @@ - com.finos.fdc3.api - fdc3api + org.finos.fdc3 + fdc3-standard ${project.version} - com.finos.fdc3.api + org.finos.fdc3 fdc3-testing ${project.version} test diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index 841bd3c1..1d57ed8e 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -4,8 +4,8 @@ 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 - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT @@ -140,7 +140,7 @@ ${project.build.directory}/npm-work -c - node_modules/.bin/quicktype -s schema --quiet --lang java --package com.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) + node_modules/.bin/quicktype -s schema --quiet --lang java --package org.finos.fdc3.context --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index 03fa2aa2..d61e4a6d 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -4,8 +4,8 @@ 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 - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT diff --git a/fdc3api/pom.xml b/fdc3-standard/pom.xml similarity index 94% rename from fdc3api/pom.xml rename to fdc3-standard/pom.xml index 7a7ee4ff..3042f144 100644 --- a/fdc3api/pom.xml +++ b/fdc3-standard/pom.xml @@ -21,13 +21,13 @@ 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 - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT - fdc3api - + fdc3-standard + FDC3 Standard UTF-8 diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/DesktopAgent.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/channel/Channel.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/channel/PrivateChannel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/channel/PrivateChannel.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Chart.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Chart.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/ChatInitSettings.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/ChatInitSettings.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Contact.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Contact.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/ContactList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/ContactList.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Context.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/ContextContants.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/ContextContants.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/ContextHelper.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/ContextHelper.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Country.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Country.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Currency.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Currency.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Email.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Email.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Instrument.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Instrument.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/InstrumentList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/InstrumentList.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Nothing.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Nothing.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Options.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Options.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Organization.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Organization.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Portfolio.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Portfolio.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Position.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Position.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/TimeRange.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/TimeRange.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/context/Valuation.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/context/Valuation.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/errors/ChannelError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/errors/ChannelError.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/errors/FDC3ConnectionException.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/errors/FDC3ConnectionException.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/errors/OpenError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/errors/OpenError.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/errors/ResolveError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/errors/ResolveError.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/errors/ResultError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/errors/ResultError.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/AppIntent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/AppIntent.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/AppMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/AppMetadata.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/ContextMetadata.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/DisplayMetadata.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/Icon.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/Icon.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/Image.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/Image.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/ImplementationMetadata.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentMetadata.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentResolution.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/metadata/IntentResolution.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/AppIdentifier.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/ContextHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/ContextHandler.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/EventHandler.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/FDC3Event.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/IntentHandler.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/IntentResult.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/types/Listener.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/types/Listener.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/utils/JacksonUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/utils/JacksonUtilities.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java diff --git a/fdc3api/src/main/java/com/finos/fdc3/api/utils/StringUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java similarity index 100% rename from fdc3api/src/main/java/com/finos/fdc3/api/utils/StringUtilities.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml index fb9aa58a..a16c8c8e 100644 --- a/fdc3-testing/pom.xml +++ b/fdc3-testing/pom.xml @@ -21,8 +21,8 @@ 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 - finos-bmo-hackathon + org.finos.fdc3 + fdc3-parent 1.0.0-SNAPSHOT @@ -44,8 +44,8 @@ - com.finos.fdc3.api - fdc3api + org.finos.fdc3.api + fdc3-standard ${project.version} diff --git a/pom.xml b/pom.xml index 8c701719..b0f85125 100644 --- a/pom.xml +++ b/pom.xml @@ -18,16 +18,16 @@ --> 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 fdc3-schema fdc3-context - fdc3api + fdc3-standard fdc3-testing fdc3-agent-proxy diff --git a/rename-directories.sh b/rename-directories.sh new file mode 100755 index 00000000..8754d0b9 --- /dev/null +++ b/rename-directories.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Script to rename directory structure from com/finos to org/finos + +echo "=== Renaming directory structure com/finos to org/finos ===" + +# Find all com/finos directories and rename them +find . -type d -path "*/com/finos" | while read dir; do + parent=$(dirname "$dir") + org_dir="$parent/org" + + echo "Processing: $dir" + + # Create org directory if it doesn't exist + mkdir -p "$org_dir" + + # Move finos directory from com to org + if [ -d "$dir" ]; then + mv "$dir" "$org_dir/" + echo " Moved to: $org_dir/finos" + fi + + # Remove empty com directory + com_dir="$parent/com" + if [ -d "$com_dir" ] && [ -z "$(ls -A "$com_dir")" ]; then + rmdir "$com_dir" + echo " Removed empty: $com_dir" + fi +done + +echo "=== Done! ===" diff --git a/rename-packages.sh b/rename-packages.sh new file mode 100755 index 00000000..dc6d5fcf --- /dev/null +++ b/rename-packages.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Script to rename com.finos to org.finos in Java files + +echo "=== Renaming com.finos to org.finos in Java files ===" + +# Find all Java files and update package declarations and imports +find . -name "*.java" -type f | while read file; do + if grep -q "com\.finos" "$file"; then + echo "Updating: $file" + sed -i '' 's/com\.finos/org.finos/g' "$file" + fi +done + +# Update pom.xml files +find . -name "pom.xml" -type f | while read file; do + if grep -q "com\.finos" "$file"; then + echo "Updating POM: $file" + sed -i '' 's/com\.finos/org.finos/g' "$file" + fi +done + +echo "=== Done! ===" diff --git a/client/pom.xml b/unused/client/pom.xml similarity index 100% rename from client/pom.xml rename to unused/client/pom.xml diff --git a/client/src/main/java/com/finos/fdc3/client/AppGUIException.java b/unused/client/src/main/java/com/finos/fdc3/client/AppGUIException.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/AppGUIException.java rename to unused/client/src/main/java/com/finos/fdc3/client/AppGUIException.java diff --git a/client/src/main/java/com/finos/fdc3/client/AppUI.java b/unused/client/src/main/java/com/finos/fdc3/client/AppUI.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/AppUI.java rename to unused/client/src/main/java/com/finos/fdc3/client/AppUI.java diff --git a/client/src/main/java/com/finos/fdc3/client/data/IncomingData.java b/unused/client/src/main/java/com/finos/fdc3/client/data/IncomingData.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/data/IncomingData.java rename to unused/client/src/main/java/com/finos/fdc3/client/data/IncomingData.java diff --git a/client/src/main/java/com/finos/fdc3/client/data/OrderData.java b/unused/client/src/main/java/com/finos/fdc3/client/data/OrderData.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/data/OrderData.java rename to unused/client/src/main/java/com/finos/fdc3/client/data/OrderData.java diff --git a/client/src/main/java/com/finos/fdc3/client/data/SnPData.java b/unused/client/src/main/java/com/finos/fdc3/client/data/SnPData.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/data/SnPData.java rename to unused/client/src/main/java/com/finos/fdc3/client/data/SnPData.java diff --git a/client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java b/unused/client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java rename to unused/client/src/main/java/com/finos/fdc3/client/handler/DataLoadHandler.java diff --git a/client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java b/unused/client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java rename to unused/client/src/main/java/com/finos/fdc3/client/handler/SystemChannelHandler.java diff --git a/client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java b/unused/client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java rename to unused/client/src/main/java/com/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java diff --git a/client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java b/unused/client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java rename to unused/client/src/main/java/com/finos/fdc3/client/model/IncomingDataTableModel.java diff --git a/client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java b/unused/client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java rename to unused/client/src/main/java/com/finos/fdc3/client/model/OrderTableModel.java diff --git a/client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java b/unused/client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java rename to unused/client/src/main/java/com/finos/fdc3/client/model/SnPTableModel.java diff --git a/client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java b/unused/client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java rename to unused/client/src/main/java/com/finos/fdc3/client/publisher/OrderMessagePublisher.java diff --git a/client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java b/unused/client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java rename to unused/client/src/main/java/com/finos/fdc3/client/utils/JSONAdapterUtil.java diff --git a/client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java b/unused/client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java rename to unused/client/src/main/java/com/finos/fdc3/client/utils/SampleDataLoaderUtil.java diff --git a/client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java b/unused/client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java rename to unused/client/src/main/java/com/finos/fdc3/client/utils/StringUtilities.java diff --git a/client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java b/unused/client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java similarity index 100% rename from client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java rename to unused/client/src/main/java/com/finos/fdc3/client/utils/SwingUtils.java 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 100% rename from openfin-fdc3-adapter/pom.xml rename to unused/openfin-fdc3-adapter/pom.xml diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannel.java diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java diff --git a/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java b/unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java similarity index 100% rename from openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java rename to unused/openfin-fdc3-adapter/src/main/java/com/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java From 48785e104d636ec607cab91c3838310568b7e355 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 14:06:47 +0000 Subject: [PATCH 08/65] Renamed packages --- fdc3-agent-proxy/pom.xml | 2 +- .../finos/fdc3/proxy/Connectable.java | 2 +- .../finos/fdc3/proxy/DesktopAgentProxy.java | 36 +++++++++---------- .../finos/fdc3/proxy/Messaging.java | 6 ++-- .../finos/fdc3/proxy/apps/AppSupport.java | 10 +++--- .../fdc3/proxy/apps/DefaultAppSupport.java | 22 ++++++------ .../fdc3/proxy/channels/ChannelSelector.java | 4 +-- .../fdc3/proxy/channels/ChannelSupport.java | 12 +++---- .../fdc3/proxy/channels/DefaultChannel.java | 16 ++++----- .../proxy/channels/DefaultChannelSupport.java | 22 ++++++------ .../proxy/channels/DefaultPrivateChannel.java | 16 ++++----- .../channels/UserChannelContextListener.java | 10 +++--- .../heartbeat/DefaultHeartbeatSupport.java | 8 ++--- .../proxy/heartbeat/HeartbeatSupport.java | 4 +-- .../intents/DefaultIntentResolution.java | 10 +++--- .../proxy/intents/DefaultIntentSupport.java | 28 +++++++-------- .../proxy/intents/IntentResolutionChoice.java | 4 +-- .../fdc3/proxy/intents/IntentResolver.java | 6 ++-- .../fdc3/proxy/intents/IntentSupport.java | 14 ++++---- .../listeners/DefaultContextListener.java | 10 +++--- .../listeners/DefaultIntentListener.java | 14 ++++---- .../listeners/DesktopAgentEventListener.java | 10 +++--- .../PrivateChannelEventListener.java | 10 +++--- .../proxy/listeners/RegisterableListener.java | 2 +- .../proxy/messaging/AbstractMessaging.java | 10 +++--- .../finos/fdc3/proxy/util/Logger.java | 2 +- .../proxy/CucumberSpringConfiguration.java | 4 +-- .../finos/fdc3/proxy/RunCucumberTest.java | 4 +-- .../proxy/steps/ChannelSelectorSteps.java | 12 +++---- .../finos/fdc3/proxy/steps/ChannelSteps.java | 16 ++++----- .../finos/fdc3/proxy/steps/GenericSteps.java | 12 +++---- .../finos/fdc3/proxy/steps/IntentSteps.java | 14 ++++---- .../finos/fdc3/proxy/steps/UtilSteps.java | 4 +-- .../finos/fdc3/proxy/support/ContextMap.java | 4 +-- .../proxy/support/TestChannelSelector.java | 6 ++-- .../fdc3/proxy/support/TestMessaging.java | 8 ++--- .../finos/fdc3/proxy/world/CustomWorld.java | 6 ++-- fdc3-context/pom.xml | 4 +-- fdc3-schema/pom.xml | 4 +-- .../java/org/finos/fdc3/api/DesktopAgent.java | 26 +++++++------- .../org/finos/fdc3/api/channel/Channel.java | 12 +++---- .../fdc3/api/channel/PrivateChannel.java | 4 +-- .../org/finos/fdc3/api/context/Chart.java | 2 +- .../fdc3/api/context/ChatInitSettings.java | 2 +- .../org/finos/fdc3/api/context/Contact.java | 2 +- .../finos/fdc3/api/context/ContactList.java | 2 +- .../org/finos/fdc3/api/context/Context.java | 4 +-- .../fdc3/api/context/ContextContants.java | 2 +- .../finos/fdc3/api/context/ContextHelper.java | 2 +- .../org/finos/fdc3/api/context/Country.java | 2 +- .../org/finos/fdc3/api/context/Currency.java | 2 +- .../org/finos/fdc3/api/context/Email.java | 2 +- .../finos/fdc3/api/context/Instrument.java | 2 +- .../fdc3/api/context/InstrumentList.java | 2 +- .../org/finos/fdc3/api/context/Nothing.java | 2 +- .../org/finos/fdc3/api/context/Options.java | 2 +- .../finos/fdc3/api/context/Organization.java | 2 +- .../org/finos/fdc3/api/context/Portfolio.java | 2 +- .../org/finos/fdc3/api/context/Position.java | 2 +- .../org/finos/fdc3/api/context/TimeRange.java | 2 +- .../org/finos/fdc3/api/context/Valuation.java | 2 +- .../finos/fdc3/api/errors/ChannelError.java | 2 +- .../api/errors/FDC3ConnectionException.java | 2 +- .../org/finos/fdc3/api/errors/OpenError.java | 2 +- .../finos/fdc3/api/errors/ResolveError.java | 2 +- .../finos/fdc3/api/errors/ResultError.java | 2 +- .../finos/fdc3/api/metadata/AppIntent.java | 2 +- .../finos/fdc3/api/metadata/AppMetadata.java | 4 +-- .../fdc3/api/metadata/ContextMetadata.java | 4 +-- .../fdc3/api/metadata/DisplayMetadata.java | 2 +- .../org/finos/fdc3/api/metadata/Icon.java | 2 +- .../org/finos/fdc3/api/metadata/Image.java | 2 +- .../api/metadata/ImplementationMetadata.java | 2 +- .../fdc3/api/metadata/IntentMetadata.java | 2 +- .../fdc3/api/metadata/IntentResolution.java | 6 ++-- .../finos/fdc3/api/types/AppIdentifier.java | 2 +- .../finos/fdc3/api/types/ContextHandler.java | 6 ++-- .../finos/fdc3/api/types/EventHandler.java | 2 +- .../org/finos/fdc3/api/types/FDC3Event.java | 2 +- .../finos/fdc3/api/types/IntentHandler.java | 6 ++-- .../finos/fdc3/api/types/IntentResult.java | 8 ++--- .../org/finos/fdc3/api/types/Listener.java | 2 +- .../fdc3/api/utils/JacksonUtilities.java | 2 +- .../finos/fdc3/api/utils/StringUtilities.java | 2 +- .../finos/fdc3/testing/FDC3Testing.java | 16 ++++----- .../fdc3/testing/agent/ChannelSelector.java | 4 +-- .../fdc3/testing/agent/IntentResolver.java | 8 ++--- .../testing/agent/SimpleChannelSelector.java | 6 ++-- .../testing/agent/SimpleIntentResolver.java | 14 ++++---- .../finos/fdc3/testing/package-info.java | 12 +++---- .../fdc3/testing/steps/GenericSteps.java | 12 +++---- .../fdc3/testing/support/MatchingUtils.java | 4 +-- .../finos/fdc3/testing/world/PropsWorld.java | 2 +- rename-directories.sh | 30 ---------------- rename-packages.sh | 22 ------------ unused/client/pom.xml | 6 ++-- .../finos/fdc3/client/AppGUIException.java | 2 +- .../{com => org}/finos/fdc3/client/AppUI.java | 18 +++++----- .../finos/fdc3/client/data/IncomingData.java | 2 +- .../finos/fdc3/client/data/OrderData.java | 2 +- .../finos/fdc3/client/data/SnPData.java | 2 +- .../fdc3/client/handler/DataLoadHandler.java | 16 ++++----- .../client/handler/SystemChannelHandler.java | 6 ++-- .../launcher/FDC3JavaAPIDemoApplication.java | 22 ++++++------ .../client/model/IncomingDataTableModel.java | 6 ++-- .../fdc3/client/model/OrderTableModel.java | 4 +-- .../fdc3/client/model/SnPTableModel.java | 4 +-- .../publisher/OrderMessagePublisher.java | 18 +++++----- .../fdc3/client/utils/JSONAdapterUtil.java | 4 +-- .../client/utils/SampleDataLoaderUtil.java | 8 ++--- .../fdc3/client/utils/StringUtilities.java | 4 +-- .../finos/fdc3/client/utils/SwingUtils.java | 4 +-- unused/openfin-fdc3-adapter/pom.xml | 4 +-- .../fdc3/adapter/openfin/OpenFinChannel.java | 12 +++---- .../OpenFinChannelDisplayMetadata.java | 6 ++-- .../OpenFinContextListenerAdapter.java | 10 +++--- .../adapter/openfin/OpenFinDesktopAgent.java | 30 ++++++++-------- .../adapter/openfin/OpenFinFDC3Config.java | 2 +- .../openfin/OpenFinIntentListenerAdapter.java | 10 +++--- 119 files changed, 409 insertions(+), 461 deletions(-) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/Connectable.java (97%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/DesktopAgentProxy.java (87%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/Messaging.java (95%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/apps/AppSupport.java (88%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/apps/DefaultAppSupport.java (96%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/ChannelSelector.java (93%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/ChannelSupport.java (90%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/DefaultChannel.java (91%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/DefaultChannelSupport.java (95%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/DefaultPrivateChannel.java (89%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/channels/UserChannelContextListener.java (93%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java (96%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java (91%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/intents/DefaultIntentResolution.java (89%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/intents/DefaultIntentSupport.java (95%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/intents/IntentResolutionChoice.java (92%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/intents/IntentResolver.java (90%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/intents/IntentSupport.java (88%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/listeners/DefaultContextListener.java (95%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/listeners/DefaultIntentListener.java (93%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java (93%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java (95%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/listeners/RegisterableListener.java (97%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/messaging/AbstractMessaging.java (96%) rename fdc3-agent-proxy/src/main/java/{com => org}/finos/fdc3/proxy/util/Logger.java (98%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/CucumberSpringConfiguration.java (93%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/RunCucumberTest.java (89%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/steps/ChannelSelectorSteps.java (90%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/steps/ChannelSteps.java (96%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/steps/GenericSteps.java (88%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/steps/IntentSteps.java (96%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/steps/UtilSteps.java (97%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/support/ContextMap.java (96%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/support/TestChannelSelector.java (95%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/support/TestMessaging.java (97%) rename fdc3-agent-proxy/src/test/java/{com => org}/finos/fdc3/proxy/world/CustomWorld.java (88%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/FDC3Testing.java (87%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/agent/ChannelSelector.java (95%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/agent/IntentResolver.java (92%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/agent/SimpleChannelSelector.java (94%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/agent/SimpleIntentResolver.java (91%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/package-info.java (62%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/steps/GenericSteps.java (97%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/support/MatchingUtils.java (99%) rename fdc3-testing/src/main/java/{com => org}/finos/fdc3/testing/world/PropsWorld.java (98%) delete mode 100755 rename-directories.sh delete mode 100755 rename-packages.sh rename unused/client/src/main/java/{com => org}/finos/fdc3/client/AppGUIException.java (96%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/AppUI.java (95%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/data/IncomingData.java (98%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/data/OrderData.java (98%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/data/SnPData.java (98%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/handler/DataLoadHandler.java (87%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/handler/SystemChannelHandler.java (95%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/launcher/FDC3JavaAPIDemoApplication.java (88%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/model/IncomingDataTableModel.java (93%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/model/OrderTableModel.java (96%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/model/SnPTableModel.java (95%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/publisher/OrderMessagePublisher.java (92%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/utils/JSONAdapterUtil.java (94%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/utils/SampleDataLoaderUtil.java (94%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/utils/StringUtilities.java (91%) rename unused/client/src/main/java/{com => org}/finos/fdc3/client/utils/SwingUtils.java (93%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinChannel.java (89%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinChannelDisplayMetadata.java (89%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinContextListenerAdapter.java (89%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinDesktopAgent.java (93%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinFDC3Config.java (97%) rename unused/openfin-fdc3-adapter/src/main/java/{com => org}/finos/fdc3/adapter/openfin/OpenFinIntentListenerAdapter.java (89%) diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index aabc3f59..bafb6d5a 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -129,7 +129,7 @@ cucumber.junit-platform.naming-strategy=long cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html - cucumber.glue=com.finos.fdc3.proxy.steps,com.finos.fdc3.proxy.world,com.finos.fdc3.testing.steps + cucumber.glue=org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps cucumber.features=src/test/resources/features diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java similarity index 97% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java index 53e07449..4afb0729 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Connectable.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy; +package org.finos.fdc3.proxy; import java.util.concurrent.CompletionStage; diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java similarity index 87% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java index 332f8e41..38930c79 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/DesktopAgentProxy.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/DesktopAgentProxy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy; +package org.finos.fdc3.proxy; import java.util.List; import java.util.Optional; @@ -22,23 +22,23 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; -import com.finos.fdc3.api.DesktopAgent; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.context.Context; -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.EventHandler; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.apps.AppSupport; -import com.finos.fdc3.proxy.channels.ChannelSupport; -import com.finos.fdc3.proxy.heartbeat.HeartbeatSupport; -import com.finos.fdc3.proxy.intents.IntentSupport; +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.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.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. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java similarity index 95% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java index 0da0606e..dcb17332 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/Messaging.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Messaging.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.finos.fdc3.proxy; +package org.finos.fdc3.proxy; import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.Predicate; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.listeners.RegisterableListener; /** * Interface for messaging between the app and the Desktop Agent. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java similarity index 88% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java index f733ec5a..4c65e4a0 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/AppSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/AppSupport.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.apps; +package org.finos.fdc3.proxy.apps; import java.util.List; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.AppMetadata; -import com.finos.fdc3.api.metadata.ImplementationMetadata; -import com.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppMetadata; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.types.AppIdentifier; /** * Interface for application-related operations. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java similarity index 96% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java index 95fab50b..7e7854ff 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/apps/DefaultAppSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.apps; +package org.finos.fdc3.proxy.apps; import java.util.ArrayList; import java.util.Collection; @@ -25,16 +25,16 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.errors.OpenError; -import com.finos.fdc3.api.errors.ResolveError; -import com.finos.fdc3.api.metadata.AppMetadata; -import com.finos.fdc3.api.metadata.Icon; -import com.finos.fdc3.api.metadata.Image; -import com.finos.fdc3.api.metadata.ImplementationMetadata; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.util.Logger; +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.Icon; +import org.finos.fdc3.api.metadata.Image; +import org.finos.fdc3.api.metadata.ImplementationMetadata; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.util.Logger; /** * Default implementation of AppSupport. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java similarity index 93% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java index 1c0f455d..7df428cf 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSelector.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.List; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.Channel; /** * Interface for channel selection UI components. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java similarity index 90% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java index dc861082..6dddcd49 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/ChannelSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSupport.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.List; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.EventHandler; -import com.finos.fdc3.api.types.Listener; +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. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java similarity index 91% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java index 83a8df2c..53e0bba2 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannel.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannel.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.HashMap; import java.util.Map; 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 com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.DefaultContextListener; +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 org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.DefaultContextListener; /** * Default implementation of a Channel. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java similarity index 95% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java index 2f30b7b1..81651357 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultChannelSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultChannelSupport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.ArrayList; import java.util.HashMap; @@ -24,16 +24,16 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.errors.ChannelError; -import com.finos.fdc3.api.metadata.DisplayMetadata; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.EventHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.DesktopAgentEventListener; -import com.finos.fdc3.proxy.util.Logger; +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.metadata.DisplayMetadata; +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.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.DesktopAgentEventListener; +import org.finos.fdc3.proxy.util.Logger; /** * Default implementation of ChannelSupport. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java similarity index 89% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java index 7381e2b0..b19fa442 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/DefaultPrivateChannel.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultPrivateChannel.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.HashMap; import java.util.Map; @@ -22,13 +22,13 @@ import java.util.concurrent.CompletionStage; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.DefaultContextListener; -import com.finos.fdc3.proxy.listeners.PrivateChannelEventListener; +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.proxy.listeners.DefaultContextListener; +import org.finos.fdc3.proxy.listeners.PrivateChannelEventListener; /** * Default implementation of a PrivateChannel. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java similarity index 93% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java index d844f844..054a791b 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/channels/UserChannelContextListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/UserChannelContextListener.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.channels; +package org.finos.fdc3.proxy.channels; import java.util.Map; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.DefaultContextListener; +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. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java similarity index 96% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java index 0776fbcd..a7cd1c11 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/DefaultHeartbeatSupport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.heartbeat; +package org.finos.fdc3.proxy.heartbeat; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -24,9 +24,9 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.RegisterableListener; -import com.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.proxy.util.Logger; /** * Default implementation of HeartbeatSupport. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java similarity index 91% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java index 5dff343e..83406473 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.heartbeat; +package org.finos.fdc3.proxy.heartbeat; -import com.finos.fdc3.proxy.Connectable; +import org.finos.fdc3.proxy.Connectable; /** * Interface for heartbeat support. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java similarity index 89% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java index 9f6949a7..490630be 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentResolution.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentResolution.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.intents; +package org.finos.fdc3.proxy.intents; import java.util.Optional; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.metadata.IntentResolution; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.IntentResult; -import com.finos.fdc3.proxy.Messaging; +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. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java similarity index 95% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java index 01713a95..0121eea6 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/DefaultIntentSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/DefaultIntentSupport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.intents; +package org.finos.fdc3.proxy.intents; import java.util.Collection; import java.util.HashMap; @@ -24,19 +24,19 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.errors.ResolveError; -import com.finos.fdc3.api.metadata.AppIntent; -import com.finos.fdc3.api.metadata.AppMetadata; -import com.finos.fdc3.api.metadata.Icon; -import com.finos.fdc3.api.metadata.Image; -import com.finos.fdc3.api.metadata.IntentMetadata; -import com.finos.fdc3.api.metadata.IntentResolution; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.DefaultIntentListener; +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.metadata.Icon; +import org.finos.fdc3.api.metadata.Image; +import org.finos.fdc3.api.metadata.IntentMetadata; +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; +import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.proxy.listeners.DefaultIntentListener; /** * Default implementation of IntentSupport. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java similarity index 92% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java index d7af5560..b9f1c263 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolutionChoice.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.intents; +package org.finos.fdc3.proxy.intents; -import com.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.AppIdentifier; /** * Represents the user's choice from an intent resolver. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java similarity index 90% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java index 40692738..6f754f83 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentResolver.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.intents; +package org.finos.fdc3.proxy.intents; import java.util.List; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; /** * Interface for intent resolution UI components. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java similarity index 88% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java index e8349818..ce6e4d3e 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/intents/IntentSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentSupport.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.intents; +package org.finos.fdc3.proxy.intents; import java.util.List; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.AppIntent; -import com.finos.fdc3.api.metadata.IntentResolution; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; +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. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java similarity index 95% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java index efcc2d21..442097ae 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultContextListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.listeners; +package org.finos.fdc3.proxy.listeners; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.types.ContextHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; /** * Default implementation of a context listener. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java similarity index 93% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java index a7f0c5a7..1c77056a 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DefaultIntentListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultIntentListener.java @@ -14,19 +14,19 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.listeners; +package org.finos.fdc3.proxy.listeners; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.ContextMetadata; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; +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.api.types.IntentHandler; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; /** * Default implementation of an intent listener. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java similarity index 93% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java index 56bd8715..f945555f 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DesktopAgentEventListener.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.listeners; +package org.finos.fdc3.proxy.listeners; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.types.EventHandler; -import com.finos.fdc3.api.types.FDC3Event; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; /** * Listener for Desktop Agent events. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java similarity index 95% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java index 794e6e17..a6d24dfa 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.listeners; +package org.finos.fdc3.proxy.listeners; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.types.EventHandler; -import com.finos.fdc3.api.types.FDC3Event; -import com.finos.fdc3.api.types.Listener; -import com.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.Messaging; /** * Event listener for private channel events. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java similarity index 97% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java index 5d2ffef7..5aceea93 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/listeners/RegisterableListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/RegisterableListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.listeners; +package org.finos.fdc3.proxy.listeners; import java.util.Map; import java.util.concurrent.CompletionStage; diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java similarity index 96% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java index f206e9d9..a20637e8 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/messaging/AbstractMessaging.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.messaging; +package org.finos.fdc3.proxy.messaging; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -25,10 +25,10 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.proxy.Messaging; -import com.finos.fdc3.proxy.listeners.RegisterableListener; -import com.finos.fdc3.proxy.util.Logger; +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; /** * Abstract base class for messaging implementations. diff --git a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java similarity index 98% rename from fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java index db243429..578dffdc 100644 --- a/fdc3-agent-proxy/src/main/java/com/finos/fdc3/proxy/util/Logger.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/Logger.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.util; +package org.finos.fdc3.proxy.util; import org.slf4j.LoggerFactory; diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java similarity index 93% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java index 094ba204..dddaf814 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/CucumberSpringConfiguration.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.proxy; +package org.finos.fdc3.proxy; -import com.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.proxy.world.CustomWorld; import io.cucumber.java.Before; import io.cucumber.java.Scenario; diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java similarity index 89% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java index 29c2af96..f51b861f 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/RunCucumberTest.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy; +package org.finos.fdc3.proxy; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -30,7 +30,7 @@ @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") -@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.finos.fdc3.proxy.steps,com.finos.fdc3.proxy.world,com.finos.fdc3.testing.steps") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps") @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty,html:target/cucumber-reports/cucumber.html") public class RunCucumberTest { } diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java similarity index 90% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java index 4f418cfd..df89927b 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSelectorSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSelectorSteps.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.steps; +package org.finos.fdc3.proxy.steps; import java.util.HashMap; import java.util.List; import java.util.Map; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.proxy.support.TestChannelSelector; -import com.finos.fdc3.proxy.support.TestMessaging; -import com.finos.fdc3.proxy.world.CustomWorld; -import com.finos.fdc3.testing.agent.SimpleIntentResolver; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.proxy.support.TestChannelSelector; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.testing.agent.SimpleIntentResolver; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java similarity index 96% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java index 57537ff0..a00fe67b 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/ChannelSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.steps; +package org.finos.fdc3.proxy.steps; import java.util.ArrayList; import java.util.HashMap; @@ -22,19 +22,19 @@ import java.util.Map; import java.util.function.Consumer; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.proxy.support.ContextMap; -import com.finos.fdc3.proxy.support.TestMessaging; -import com.finos.fdc3.proxy.world.CustomWorld; -import com.finos.fdc3.testing.agent.SimpleChannelSelector; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.proxy.support.ContextMap; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.testing.agent.SimpleChannelSelector; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import static com.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static com.finos.fdc3.testing.support.MatchingUtils.matchData; +import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static org.finos.fdc3.testing.support.MatchingUtils.matchData; /** * Cucumber step definitions for channel-related tests. diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java similarity index 88% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java index 0cd60085..739805fb 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/GenericSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.steps; +package org.finos.fdc3.proxy.steps; import java.util.HashMap; import java.util.List; import java.util.Map; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.proxy.support.TestMessaging; -import com.finos.fdc3.proxy.world.CustomWorld; -import com.finos.fdc3.testing.agent.SimpleChannelSelector; -import com.finos.fdc3.testing.agent.SimpleIntentResolver; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.testing.agent.SimpleChannelSelector; +import org.finos.fdc3.testing.agent.SimpleIntentResolver; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java similarity index 96% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java index 05122b84..ef5c0855 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/IntentSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/IntentSteps.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.steps; +package org.finos.fdc3.proxy.steps; import java.time.Instant; import java.util.ArrayList; @@ -25,15 +25,15 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.ContextMetadata; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.proxy.support.TestMessaging; -import com.finos.fdc3.proxy.world.CustomWorld; +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.support.TestMessaging; +import org.finos.fdc3.proxy.world.CustomWorld; import io.cucumber.java.en.Given; -import static com.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; /** * Cucumber step definitions for intent-related tests. diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java similarity index 97% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java index 70b568d4..175206d2 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/steps/UtilSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/UtilSteps.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.steps; +package org.finos.fdc3.proxy.steps; -import com.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.proxy.world.CustomWorld; import io.cucumber.java.en.When; diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java similarity index 96% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java index 8edf3f54..8abaf665 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/ContextMap.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ContextMap.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.support; +package org.finos.fdc3.proxy.support; import java.util.HashMap; import java.util.Map; -import com.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.context.Context; /** * Pre-defined context objects for testing. diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java similarity index 95% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java index fc5e5e5d..0cc47f60 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestChannelSelector.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.support; +package org.finos.fdc3.proxy.support; import java.util.ArrayList; import java.util.List; @@ -22,8 +22,8 @@ import java.util.concurrent.CompletionStage; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.testing.agent.ChannelSelector; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.testing.agent.ChannelSelector; /** * Test implementation of ChannelSelector for Cucumber tests. diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java similarity index 97% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java index f1cc6bd9..146641f7 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/support/TestMessaging.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestMessaging.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.support; +package org.finos.fdc3.proxy.support; import java.time.Instant; import java.util.ArrayList; @@ -24,9 +24,9 @@ import java.util.UUID; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.types.AppIdentifier; /** * Test implementation of messaging for Cucumber tests. diff --git a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java similarity index 88% rename from fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java index a95ade94..90879ced 100644 --- a/fdc3-agent-proxy/src/test/java/com/finos/fdc3/proxy/world/CustomWorld.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/world/CustomWorld.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.finos.fdc3.proxy.world; +package org.finos.fdc3.proxy.world; -import com.finos.fdc3.proxy.support.TestMessaging; -import com.finos.fdc3.testing.world.PropsWorld; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.testing.world.PropsWorld; /** * Custom Cucumber World for agent-proxy tests. diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index 1d57ed8e..caf1715d 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -71,7 +71,7 @@ - + @@ -140,7 +140,7 @@ ${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/com/finos/fdc3/context/ContextTypes.java -S node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) + 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 node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index d61e4a6d..76546a4f 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -71,7 +71,7 @@ - + @@ -158,7 +158,7 @@ ${project.build.directory}/npm-work -c - node_modules/.bin/quicktype -s schema --quiet --lang java --package com.finos.fdc3.schema.api --out ${project.build.directory}/generated-sources/fdc3/com/finos/fdc3/schema/api/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) + node_modules/.bin/quicktype -s schema --quiet --lang java --package org.finos.fdc3.schema --out ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index 5330b04f..f939c524 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package com.finos.fdc3.api; +package org.finos.fdc3.api; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.channel.PrivateChannel; -import com.finos.fdc3.api.context.Context; -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.EventHandler; -import com.finos.fdc3.api.types.IntentHandler; -import com.finos.fdc3.api.types.Listener; +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.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 java.util.List; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java index e31aea9e..2a44f748 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.finos.fdc3.api.channel; +package org.finos.fdc3.api.channel; import java.util.Optional; import java.util.concurrent.CompletionStage; -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.IntentResult; -import com.finos.fdc3.api.types.Listener; +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.IntentResult; +import org.finos.fdc3.api.types.Listener; /** * Object representing a context channel. diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java index cb2c8c41..79ad06fd 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.finos.fdc3.api.channel; +package org.finos.fdc3.api.channel; import java.util.Optional; import java.util.function.Consumer; -import com.finos.fdc3.api.types.Listener; +import org.finos.fdc3.api.types.Listener; /** * Object representing a private context channel, which is intended to support secure communication between applications, and diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java index 708f8b44..46c58bc2 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java index bdcfe578..41d8e01c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java index e0705abb..465b731a 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java index ae56c8b4..2ec72e5c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java index 9d087b02..47580672 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.finos.fdc3.api.utils.StringUtilities; +import org.finos.fdc3.api.utils.StringUtilities; import java.util.HashMap; import java.util.Map; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java index 73752230..e55eff7b 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import java.util.Collections; import java.util.Map; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java index a5898e74..8630bba1 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import java.util.HashSet; import java.util.Map; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java index 45f5b56e..653c6f73 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import java.util.Map; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java index 16ce322b..5820d731 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java index 26ba765b..d0edb904 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java index 0822d9af..e98db712 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java index 7e1bef4c..afbfa900 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java index 700ee8dd..5bd4169c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java index f0863b7e..5903eec7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java index d288e629..9161cf79 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java index 9464ef33..a90fc482 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java index b62cc0c0..63ade9da 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java index e8e0b3f1..375cc126 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java index 6023f212..9205671c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.context; +package org.finos.fdc3.api.context; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java index b274127d..25a30636 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ChannelError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.errors; +package org.finos.fdc3.api.errors; public enum ChannelError { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java index 6fe806b6..858ca1ee 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/FDC3ConnectionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.errors; +package org.finos.fdc3.api.errors; /** * Exception used for errors connecting to FDC3 desktop agent diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java index 223e860a..427432f9 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/OpenError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.errors; +package org.finos.fdc3.api.errors; public enum OpenError { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java index 3e70a862..44e2b97e 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResolveError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.errors; +package org.finos.fdc3.api.errors; public enum ResolveError { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java index 0c37a229..9d61a9ab 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/errors/ResultError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.errors; +package org.finos.fdc3.api.errors; public enum ResultError { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java index 263e2ca0..ab770c50 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Collection; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java index 292727bf..a4256494 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Collection; import java.util.Map; import java.util.Optional; -import com.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.AppIdentifier; /** * 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. diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index a6405ed8..466e5600 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; -import com.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.AppIdentifier; /** * Metadata relating to a context or intent and context received through the diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java index 834398f1..b05dab94 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java index 5ad8fbc8..674b1431 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java index 82434efb..c5d260d7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java index 61ac81b7..8e19e1c9 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java index 6297789f..eff3207e 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; /** * Intent descriptor diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java index 3d80ae44..3fba50f7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.finos.fdc3.api.metadata; +package org.finos.fdc3.api.metadata; import java.util.Optional; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.types.AppIdentifier; -import com.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.api.types.IntentResult; /** * IntentResolution provides a standard format for data returned upon resolving an intent. diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java index 16a30bf0..fddffb24 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; import java.util.Optional; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java index 1cba6622..9708bcb2 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; @FunctionalInterface public interface ContextHandler { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java index 2490100c..d20f391e 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; /** * Describes a callback that handles non-context and non-intent events from the diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java index 4a4efef7..a7ec007b 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; /** * Type representing the event object passed to event handlers subscribed to diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java index 579e7b58..941d7472 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; import java.util.Optional; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; /** * Describes a callback that handles a context event and may return a promise of a Context or Channel object to be returned to the diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java index fcd7806e..2deef0e7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.IntentResolution; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.IntentResolution; /** * Describes results that an {@link IntentHandler} may optionally return that should be communicated back to the app that raised the diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java index 1bcbe60b..c4b6911c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.types; +package org.finos.fdc3.api.types; public interface Listener { /** diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java index 368caa9b..613b9a17 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.utils; +package org.finos.fdc3.api.utils; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java index 77af1ed7..ea937432 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.api.utils; +package org.finos.fdc3.api.utils; public class StringUtilities { diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java similarity index 87% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java index f134a26c..e3c37b5a 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/FDC3Testing.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.testing; +package org.finos.fdc3.testing; -import com.finos.fdc3.testing.agent.ChannelSelector; -import com.finos.fdc3.testing.agent.IntentResolver; -import com.finos.fdc3.testing.agent.SimpleChannelSelector; -import com.finos.fdc3.testing.agent.SimpleIntentResolver; -import com.finos.fdc3.testing.steps.GenericSteps; -import com.finos.fdc3.testing.support.MatchingUtils; -import com.finos.fdc3.testing.world.PropsWorld; +import org.finos.fdc3.testing.agent.ChannelSelector; +import org.finos.fdc3.testing.agent.IntentResolver; +import org.finos.fdc3.testing.agent.SimpleChannelSelector; +import org.finos.fdc3.testing.agent.SimpleIntentResolver; +import org.finos.fdc3.testing.steps.GenericSteps; +import org.finos.fdc3.testing.support.MatchingUtils; +import org.finos.fdc3.testing.world.PropsWorld; /** * Main entry point for the FDC3 Testing framework. diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java similarity index 95% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java index 3c926849..bce2bd1f 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/ChannelSelector.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.finos.fdc3.testing.agent; +package org.finos.fdc3.testing.agent; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.api.channel.Channel; /** * Interface for selecting channels. diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java similarity index 92% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java index 82101ab0..6f5db0f6 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/IntentResolver.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.testing.agent; +package org.finos.fdc3.testing.agent; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.AppIntent; -import com.finos.fdc3.api.types.IntentResult; +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppIntent; +import org.finos.fdc3.api.types.IntentResult; /** * Interface for resolving intents to specific applications. diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java similarity index 94% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java index 0abdb581..cda8aaa3 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleChannelSelector.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.finos.fdc3.testing.agent; +package org.finos.fdc3.testing.agent; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; -import com.finos.fdc3.api.channel.Channel; -import com.finos.fdc3.testing.world.PropsWorld; +import org.finos.fdc3.api.channel.Channel; +import org.finos.fdc3.testing.world.PropsWorld; /** * A simple channel selector for testing purposes. diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java similarity index 91% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java index f1be263d..b237e191 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/agent/SimpleIntentResolver.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.testing.agent; +package org.finos.fdc3.testing.agent; import java.util.ArrayList; import java.util.List; @@ -22,12 +22,12 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import com.finos.fdc3.api.context.Context; -import com.finos.fdc3.api.metadata.AppIntent; -import com.finos.fdc3.api.metadata.AppMetadata; -import com.finos.fdc3.api.metadata.IntentMetadata; -import com.finos.fdc3.api.types.IntentResult; -import com.finos.fdc3.testing.world.PropsWorld; +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.IntentResult; +import org.finos.fdc3.testing.world.PropsWorld; /** * A simple intent resolver for testing purposes. diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java similarity index 62% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java index 89457dc2..7b854603 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/package-info.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java @@ -7,14 +7,14 @@ *

* Main components: *

    - *
  • {@link com.finos.fdc3.testing.world.PropsWorld} - Cucumber World class for test state
  • - *
  • {@link com.finos.fdc3.testing.steps.GenericSteps} - Generic Cucumber step definitions
  • - *
  • {@link com.finos.fdc3.testing.support.MatchingUtils} - Utilities for matching test data
  • - *
  • {@link com.finos.fdc3.testing.agent.SimpleIntentResolver} - Simple intent resolver for testing
  • - *
  • {@link com.finos.fdc3.testing.agent.SimpleChannelSelector} - Simple channel selector for testing
  • + *
  • {@link org.finos.fdc3.testing.world.PropsWorld} - Cucumber World class for test state
  • + *
  • {@link org.finos.fdc3.testing.steps.GenericSteps} - Generic Cucumber step definitions
  • + *
  • {@link org.finos.fdc3.testing.support.MatchingUtils} - Utilities for matching test data
  • + *
  • {@link org.finos.fdc3.testing.agent.SimpleIntentResolver} - Simple intent resolver for testing
  • + *
  • {@link org.finos.fdc3.testing.agent.SimpleChannelSelector} - Simple channel selector for testing
  • *
* * @see FDC3 Standard */ -package com.finos.fdc3.testing; +package org.finos.fdc3.testing; diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java similarity index 97% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 36e43396..7be72aaf 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.testing.steps; +package org.finos.fdc3.testing.steps; import java.lang.reflect.Method; import java.util.List; @@ -25,17 +25,17 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import com.finos.fdc3.testing.support.MatchingUtils; -import com.finos.fdc3.testing.world.PropsWorld; +import org.finos.fdc3.testing.support.MatchingUtils; +import org.finos.fdc3.testing.world.PropsWorld; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import static com.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; -import static com.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static com.finos.fdc3.testing.support.MatchingUtils.matchData; +import static org.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; +import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static org.finos.fdc3.testing.support.MatchingUtils.matchData; import static org.junit.jupiter.api.Assertions.*; /** diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java similarity index 99% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 97b41b64..7dd6f9a3 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.testing.support; +package org.finos.fdc3.testing.support; import java.util.ArrayList; import java.util.List; @@ -24,7 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.finos.fdc3.testing.world.PropsWorld; +import org.finos.fdc3.testing.world.PropsWorld; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.PathNotFoundException; import com.networknt.schema.JsonSchema; diff --git a/fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java similarity index 98% rename from fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java rename to fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java index b4a07fb8..6e3f38c2 100644 --- a/fdc3-testing/src/main/java/com/finos/fdc3/testing/world/PropsWorld.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.finos.fdc3.testing.world; +package org.finos.fdc3.testing.world; import java.util.HashMap; import java.util.Map; diff --git a/rename-directories.sh b/rename-directories.sh deleted file mode 100755 index 8754d0b9..00000000 --- a/rename-directories.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Script to rename directory structure from com/finos to org/finos - -echo "=== Renaming directory structure com/finos to org/finos ===" - -# Find all com/finos directories and rename them -find . -type d -path "*/com/finos" | while read dir; do - parent=$(dirname "$dir") - org_dir="$parent/org" - - echo "Processing: $dir" - - # Create org directory if it doesn't exist - mkdir -p "$org_dir" - - # Move finos directory from com to org - if [ -d "$dir" ]; then - mv "$dir" "$org_dir/" - echo " Moved to: $org_dir/finos" - fi - - # Remove empty com directory - com_dir="$parent/com" - if [ -d "$com_dir" ] && [ -z "$(ls -A "$com_dir")" ]; then - rmdir "$com_dir" - echo " Removed empty: $com_dir" - fi -done - -echo "=== Done! ===" diff --git a/rename-packages.sh b/rename-packages.sh deleted file mode 100755 index dc6d5fcf..00000000 --- a/rename-packages.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Script to rename com.finos to org.finos in Java files - -echo "=== Renaming com.finos to org.finos in Java files ===" - -# Find all Java files and update package declarations and imports -find . -name "*.java" -type f | while read file; do - if grep -q "com\.finos" "$file"; then - echo "Updating: $file" - sed -i '' 's/com\.finos/org.finos/g' "$file" - fi -done - -# Update pom.xml files -find . -name "pom.xml" -type f | while read file; do - if grep -q "com\.finos" "$file"; then - echo "Updating POM: $file" - sed -i '' 's/com\.finos/org.finos/g' "$file" - fi -done - -echo "=== Done! ===" diff --git a/unused/client/pom.xml b/unused/client/pom.xml index 4bb2da63..452aae95 100644 --- a/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/openfin-fdc3-adapter/pom.xml b/unused/openfin-fdc3-adapter/pom.xml index c7672069..b4258aa6 100644 --- a/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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/unused/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 unused/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/unused/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; From 4573e6f6397c7f6d970b965892b6bcaf560823fa Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 14:17:09 +0000 Subject: [PATCH 09/65] removed hand-written contexts --- .../java/org/finos/fdc3/api/DesktopAgent.java | 2 +- .../org/finos/fdc3/api/context/Chart.java | 87 ------------ .../fdc3/api/context/ChatInitSettings.java | 79 ----------- .../org/finos/fdc3/api/context/Contact.java | 37 ----- .../finos/fdc3/api/context/ContactList.java | 48 ------- .../org/finos/fdc3/api/context/Context.java | 133 ------------------ .../fdc3/api/context/ContextContants.java | 35 ----- .../finos/fdc3/api/context/ContextHelper.java | 69 --------- .../org/finos/fdc3/api/context/Country.java | 39 ----- .../org/finos/fdc3/api/context/Currency.java | 35 ----- .../org/finos/fdc3/api/context/Email.java | 68 --------- .../finos/fdc3/api/context/Instrument.java | 44 ------ .../fdc3/api/context/InstrumentList.java | 48 ------- .../org/finos/fdc3/api/context/Nothing.java | 30 ---- .../org/finos/fdc3/api/context/Options.java | 92 ------------ .../finos/fdc3/api/context/Organization.java | 38 ----- .../org/finos/fdc3/api/context/Portfolio.java | 48 ------- .../org/finos/fdc3/api/context/Position.java | 61 -------- .../org/finos/fdc3/api/context/TimeRange.java | 56 -------- .../org/finos/fdc3/api/context/Valuation.java | 94 ------------- .../finos/fdc3/api/types/ContextHandler.java | 2 +- .../finos/fdc3/api/types/IntentHandler.java | 2 +- .../finos/fdc3/api/types/IntentResult.java | 2 +- 23 files changed, 4 insertions(+), 1145 deletions(-) delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index f939c524..c13ebe85 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -18,7 +18,7 @@ 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.schema.Context; import org.finos.fdc3.api.metadata.AppIntent; import org.finos.fdc3.api.metadata.AppMetadata; import org.finos.fdc3.api.metadata.ImplementationMetadata; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java deleted file mode 100644 index 46c58bc2..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Chart.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.List; -import java.util.Set; - -@JsonPropertyOrder({ - "type", - "instruments", - "range", - "style", - "otherConfig" -}) -public class Chart extends Context { - public static String TYPE = "fdc3.chart"; - public static Set STYLE = Set.of("line", "bar", "stacked-bar", "mountain", "candle", "pie", "scatter", "histogram", "heatmap", "custom"); - - @JsonProperty("instruments") - private List instruments; - - @JsonProperty("range") - private TimeRange range; - - @JsonProperty("style") - private String style; - - @JsonProperty("otherConfig") - private Object otherConfig; - - public Chart(List instruments) { - super(TYPE); - this.instruments = instruments; - } - - public List getInstruments() { - return instruments; - } - - public void setInstruments(List instruments) { - this.instruments = instruments; - } - - public TimeRange getRange() { - return range; - } - - public void setRange(TimeRange range) { - this.range = range; - } - - public String getStyle() { - return style; - } - - public void setStyle(String style) { - if(!STYLE.contains(style)) { - throw new IllegalArgumentException("Invalid Style argument!"); - } - this.style = style; - } - - public Object getOtherConfig() { - return otherConfig; - } - - public void setOtherConfig(Object otherConfig) { - this.otherConfig = otherConfig; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java deleted file mode 100644 index 41d8e01c..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ChatInitSettings.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonPropertyOrder({ - "type", - "chatName", - "members", - "initMessage", - "options" -}) -public class ChatInitSettings extends Context { - public static String TYPE = "fdc3.chat.initSettings"; - - @JsonProperty("chatName") - private String chatName; - - @JsonProperty("members") - private ContactList members; - - @JsonProperty("initMessage") - private String initMessage; - - @JsonProperty("options") - private Options options; - - public ChatInitSettings() { - super(TYPE); - } - - public String getChatName() { - return chatName; - } - - public void setChatName(String chatName) { - this.chatName = chatName; - } - - public ContactList getMembers() { - return members; - } - - public void setMembers(ContactList members) { - this.members = members; - } - - public String getInitMessage() { - return initMessage; - } - - public void setInitMessage(String initMessage) { - this.initMessage = initMessage; - } - - public Options getOptions() { - return options; - } - - public void setOptions(Options options) { - this.options = options; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java deleted file mode 100644 index 465b731a..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Contact.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.Map; - -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Contact extends Context { - public static String TYPE = "fdc3.contact"; - public static Map VALID_ID_KEYS = Map.of( - "email", false, - "FDS_ID", false); - - public Contact() { - super(TYPE); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java deleted file mode 100644 index 2ec72e5c..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContactList.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.List; - -@JsonPropertyOrder({ - "type", - "name", - "id", - "contacts" -}) -public class ContactList extends Context { - public static String TYPE = "fdc3.contactList"; - - @JsonProperty("contacts") - private List contacts; - - public ContactList(List contacts) { - super(TYPE); - this.contacts = contacts; - } - - public List getContacts() { - return contacts; - } - - public void setContacts(List contacts) { - this.contacts = contacts; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java deleted file mode 100644 index 47580672..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.finos.fdc3.api.utils.StringUtilities; - -import java.util.HashMap; -import java.util.Map; - -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Context { - @JsonProperty("type") - private String type; - - @JsonProperty("name") - private String name; - - @JsonProperty("id") - private Map id; - - public Context() { - } - - public Context(String type) { - this.type = type; - this.name = null; - this.id = null; - } - - public Context(String type, String name) { - this.type = type; - this.name = name; - this.id = null; - } - - public Context(String type, Map id) { - this.type = type; - this.name = null; - this.setId(id); - } - - public Context(String type, String name, Map id) { - this.type = type; - this.name = name; - this.setId(id); - } - - public String getType() { - return type; - } - - public Map getId() { - return id; - } - - public void setId(Map id) { - /* - * Map keyMap = - * ContextContants.CONTEXT_ID_KEYS_MAPPING.getOrDefault(type, null); - * if(!ContextHelper.hasValidKeys(id, keyMap)) { - * throw new IllegalArgumentException("Invalid ID(s) present!"); - * } - * if(!ContextHelper.hasMandatoryKeys(id, keyMap)) { - * throw new IllegalArgumentException("Mandatory ID(s) missing!"); - * } - */ - - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return StringUtilities.valueAsString(this); - } - - /** - * Convert this Context to a Map for JSON serialization. - */ - public Map toMap() { - Map map = new HashMap<>(); - map.put("type", type); - if (name != null) { - map.put("name", name); - } - if (id != null) { - map.put("id", id); - } - return map; - } - - /** - * Create a Context from a Map. - */ - @SuppressWarnings("unchecked") - public static Context fromMap(Map map) { - if (map == null) { - return null; - } - String type = (String) map.get("type"); - String name = (String) map.get("name"); - Map id = (Map) map.get("id"); - return new Context(type, name, id); - } - -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java deleted file mode 100644 index e55eff7b..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextContants.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import java.util.Collections; -import java.util.Map; - -public class ContextContants { - - - public static Map> CONTEXT_ID_KEYS_MAPPING = Map.of( - Contact.TYPE, Contact.VALID_ID_KEYS, - Country.TYPE, Country.VALID_ID_KEYS, - Currency.TYPE, Currency.VALID_ID_KEYS, - Instrument.TYPE, Instrument.VALID_ID_KEYS, - Organization.TYPE, Organization.VALID_ID_KEYS, - ContactList.TYPE, Collections.emptyMap(), - InstrumentList.TYPE, Collections.emptyMap() - ); - -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java deleted file mode 100644 index 8630bba1..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/ContextHelper.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class ContextHelper { - public static Set getMandatoryIdKeys(Map validIdKeys) { - Set mandatoryIdKeys = new HashSet<>(); - if(validIdKeys == null) { - return mandatoryIdKeys; - } - for(Map.Entry entry : validIdKeys.entrySet()) { - if(entry.getValue()) { - mandatoryIdKeys.add(entry.getKey()); - } - } - return mandatoryIdKeys; - } - - public static boolean hasMandatoryKeys(Map idMap, Map validIdKeys) { - Set mandatoryIdKeys = ContextHelper.getMandatoryIdKeys(validIdKeys); - if(mandatoryIdKeys.isEmpty()) { - return true; - } else if (idMap == null) { - return false; - } - for(Object key : mandatoryIdKeys) { - if(!idMap.containsKey(key)) { - return false; - } - } - return true; - } - - public static boolean hasValidKeys(Map idMap, Map validIdKeys) { - if (idMap == null) { - return true; - } - if (validIdKeys == null) { - return false; - } - if (validIdKeys.isEmpty()) { - return true; - } - for(Object key: idMap.keySet()) { - if(!validIdKeys.containsKey(key)) { - return false; - } - } - return true; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java deleted file mode 100644 index 653c6f73..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Country.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@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); - - public Country(Map id, String name) { - super(TYPE, name, id); - } - - public Country(Map id) { - super(TYPE, id); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java deleted file mode 100644 index 5820d731..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Currency.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.Map; - -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Currency extends Context { - public static String TYPE = "fdc3.currency"; - public static Map VALID_ID_KEYS = Map.of("CURRENCY_ISOCODE", true); - - public Currency() { - super(TYPE); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java deleted file mode 100644 index d0edb904..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Email.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonPropertyOrder({ - "type", - "recipients", - "subject", - "textBody" -}) -public class Email extends Context { - public static String TYPE = "fdc3.email"; - - @JsonProperty("recipients") - private ContactList recipients; - - @JsonProperty("subject") - private String subject; - - @JsonProperty("textBody") - private String textBody; - - public Email(ContactList recipients) { - super(TYPE); - this.recipients = recipients; - } - - public ContactList getRecipients() { - return recipients; - } - - public void setRecipients(ContactList recipients) { - this.recipients = recipients; - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public String getTextBody() { - return textBody; - } - - public void setTextBody(String textBody) { - this.textBody = textBody; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java deleted file mode 100644 index e98db712..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Instrument.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.Map; - -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Instrument extends Context { - public static String TYPE = "fdc3.instrument"; - public static Map VALID_ID_KEYS = Map.of( - "FDS_ID", false, - "PERMID", false, - "RIC", false, - "BBG", false, - "ticker", false, - "FIGI", false, - "ISIN", false, - "CUSIP", false, - "SEDOL", false); - - public Instrument() { - super(TYPE); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java deleted file mode 100644 index afbfa900..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/InstrumentList.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.List; - -@JsonPropertyOrder({ - "type", - "name", - "id", - "instruments" -}) -public class InstrumentList extends Context { - public static String TYPE = "fdc3.instrumentList"; - - @JsonProperty("instruments") - private List instruments; - - public InstrumentList(List instruments) { - super(TYPE); - this.instruments = instruments; - } - - public List getInstruments() { - return instruments; - } - - public void setInstruments(List instruments) { - this.instruments = instruments; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java deleted file mode 100644 index 5bd4169c..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Nothing.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonPropertyOrder({ - "type" -}) -public class Nothing extends Context { - public static String TYPE = "fdc3.nothing"; - - public Nothing() { - super(TYPE); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java deleted file mode 100644 index 5903eec7..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Options.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonPropertyOrder({ - "groupRecipients", - "public", - "allowHistoryBrowsing", - "allowMessageCopy", - "allowAddUser", -}) -public class Options { - @JsonProperty("groupRecipients") - private boolean groupRecipients; - - @JsonProperty("public") - private boolean publicFlag; - - @JsonProperty("allowHistoryBrowsing") - private boolean allowHistoryBrowsing; - - @JsonProperty("allowMessageCopy") - private boolean allowMessageCopy; - - @JsonProperty("allowAddUser") - private boolean allowAddUser; - - public Options() { - this.groupRecipients = false; - this.publicFlag = false; - this.allowHistoryBrowsing = false; - this.allowMessageCopy = false; - this.allowAddUser = false; - } - - public boolean isGroupRecipients() { - return groupRecipients; - } - - public void setGroupRecipients(boolean groupRecipients) { - this.groupRecipients = groupRecipients; - } - - public boolean isPublicFlag() { - return publicFlag; - } - - public void setPublicFlag(boolean publicFlag) { - this.publicFlag = publicFlag; - } - - public boolean isAllowHistoryBrowsing() { - return allowHistoryBrowsing; - } - - public void setAllowHistoryBrowsing(boolean allowHistoryBrowsing) { - this.allowHistoryBrowsing = allowHistoryBrowsing; - } - - public boolean isAllowMessageCopy() { - return allowMessageCopy; - } - - public void setAllowMessageCopy(boolean allowMessageCopy) { - this.allowMessageCopy = allowMessageCopy; - } - - public boolean isAllowAddUser() { - return allowAddUser; - } - - public void setAllowAddUser(boolean allowAddUser) { - this.allowAddUser = allowAddUser; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java deleted file mode 100644 index 9161cf79..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Organization.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.Map; - -@JsonPropertyOrder({ - "type", - "name", - "id" -}) -public class Organization extends Context { - public static String TYPE = "fdc3.organization"; - public static Map VALID_ID_KEYS = Map.of( - "LEI", false, - "PERMID", false, - "FDS_ID", false); - - public Organization() { - super(TYPE); - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java deleted file mode 100644 index a90fc482..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Portfolio.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.util.List; - -@JsonPropertyOrder({ - "type", - "id", - "name", - "positions" -}) -public class Portfolio extends Context { - public static String TYPE = "fdc3.portfolio"; - - @JsonProperty("positions") - private List positions; - - public Portfolio(List positions) { - super(TYPE); - this.positions = positions; - } - - public List getPositions() { - return positions; - } - - public void setPositions(List positions) { - this.positions = positions; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java deleted file mode 100644 index 63ade9da..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Position.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import java.math.BigDecimal; - -@JsonPropertyOrder({ - "type", - "id", - "name", - "holding", - "instrument" -}) -public class Position extends Context { - public static String TYPE = "fdc3.position"; - - @JsonProperty("holding") - private BigDecimal holding; - - @JsonProperty("instrument") - private Instrument instrument; - - public Position(BigDecimal holding, Instrument instrument) { - super(TYPE); - this.holding = holding; - this.instrument = instrument; - } - - public BigDecimal getHolding() { - return holding; - } - - public void setHolding(BigDecimal holding) { - this.holding = holding; - } - - public Instrument getInstrument() { - return instrument; - } - - public void setInstrument(Instrument instrument) { - this.instrument = instrument; - } -} \ No newline at end of file diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java deleted file mode 100644 index 375cc126..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/TimeRange.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.joda.time.DateTime; - -@JsonPropertyOrder({ - "type", - "startTime", - "endTime" -}) -public class TimeRange extends Context { - public static String TYPE = "fdc3.timeRange"; - - @JsonProperty("startTime") - private DateTime startTime; - - @JsonProperty("endTime") - private DateTime endTime; - - public TimeRange() { - super(TYPE); - } - - public DateTime getStartTime() { - return startTime; - } - - public void setStartTime(DateTime startTime) { - this.startTime = startTime; - } - - public DateTime getEndTime() { - return endTime; - } - - public void setEndTime(DateTime endTime) { - this.endTime = endTime; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java deleted file mode 100644 index 9205671c..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Valuation.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.context; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.joda.time.DateTime; - -@JsonPropertyOrder({ - "type", - "value", - "price", - "CURRENCY_ISOCODE", - "valuationTime", - "expiryTime" -}) -public class Valuation extends Context { - public static String TYPE = "fdc3.valuation"; - - @JsonProperty("value") - private Double value; - - @JsonProperty("price") - private Double price; - - @JsonProperty("CURRENCY_ISOCODE") - private String currencyIsocode; - - @JsonProperty("valuationTime") - private DateTime valuationTime; - - @JsonProperty("expiryTime") - private DateTime expiryTime; - - public Valuation(Double value, String currencyIsocode) { - super(TYPE); - this.value = value; - this.currencyIsocode = currencyIsocode; - } - - public Double getValue() { - return value; - } - - public void setValue(Double value) { - this.value = value; - } - - public Double getPrice() { - return price; - } - - public void setPrice(Double price) { - this.price = price; - } - - public String getCurrencyIsocode() { - return currencyIsocode; - } - - public void setCurrencyIsocode(String currencyIsocode) { - this.currencyIsocode = currencyIsocode; - } - - public DateTime getValuationTime() { - return valuationTime; - } - - public void setValuationTime(DateTime valuationTime) { - this.valuationTime = valuationTime; - } - - public DateTime getExpiryTime() { - return expiryTime; - } - - public void setExpiryTime(DateTime expiryTime) { - this.expiryTime = expiryTime; - } -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java index 9708bcb2..d7a3f5a1 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java @@ -16,7 +16,7 @@ package org.finos.fdc3.api.types; -import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.schema.Context; import org.finos.fdc3.api.metadata.ContextMetadata; @FunctionalInterface diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java index 941d7472..0da951d6 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java @@ -19,7 +19,7 @@ import java.util.Optional; import java.util.concurrent.CompletionStage; -import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.schema.Context; import org.finos.fdc3.api.metadata.ContextMetadata; /** diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java index 2deef0e7..c499744f 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java @@ -16,7 +16,7 @@ package org.finos.fdc3.api.types; import org.finos.fdc3.api.channel.Channel; -import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.schema.Context; import org.finos.fdc3.api.metadata.IntentResolution; /** From e6d466a7e5c70d1558d616298b82d0662a86c6c8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 14:42:29 +0000 Subject: [PATCH 10/65] Working on unit tests for context objects --- fdc3-context/pom.xml | 36 +++ .../finos/fdc3/context/ContextConverter.java | 152 ++++++++++++ .../fdc3/context/ContextRoundTripTest.java | 223 ++++++++++++++++++ 3 files changed, 411 insertions(+) create mode 100644 fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java create mode 100644 fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index caf1715d..b0efca7a 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -23,6 +23,13 @@ + + + org.finos.fdc3 + fdc3-schema + ${project.version} + + com.fasterxml.jackson.core @@ -34,6 +41,11 @@ jackson-databind 2.16.1 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.16.1 + @@ -144,9 +156,33 @@ + + + fix-context-imports + generate-sources + + exec + + + /bin/sh + ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/context + + -c + rm -f Context.java 2>/dev/null; sed -i '' 's/^package org.finos.fdc3.context;$/package org.finos.fdc3.context;\ +import org.finos.fdc3.schema.Context;/' Converter.java || true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.3 + + org.codehaus.mojo 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..37bedf03 --- /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.schema.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/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..ff4bbdc0 --- /dev/null +++ b/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java @@ -0,0 +1,223 @@ +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.Set; +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() { + // Find the schemas directory - it's in target/npm-work/node_modules/@finos/fdc3-context/dist/schemas/context + String basePath = System.getProperty("user.dir"); + 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", "npm-work", "node_modules", + "@finos", "fdc3-context", "dist", "schemas", "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 + ")"); + } + + // Fields that are known to be lost in nested contexts due to ContextElement limitations + private static final Set KNOWN_NESTED_CONTEXT_FIELDS = Set.of("instruments", "range", "style"); + + /** + * 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()) { + // Skip known fields that are lost in nested ContextElement serialization + if (reserializedValue == null && context.contains(".context") && + KNOWN_NESTED_CONTEXT_FIELDS.contains(fieldName)) { + System.out.println(" [KNOWN ISSUE] Nested context field '" + fieldName + + "' lost in " + context + " (ContextElement limitation)"); + return; + } + + 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); + } + } +} + From 5fd8d05f75cb5ed45e41c0b4fd1e98409a5fb85b Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 14:56:25 +0000 Subject: [PATCH 11/65] fixing context issue still --- fdc3-context/pom.xml | 9 +- .../finos/fdc3/context/ContextConverter.java | 2 +- fdc3-schema/pom.xml | 24 +++ fdc3-standard/pom.xml | 6 +- .../java/org/finos/fdc3/api/DesktopAgent.java | 2 +- .../org/finos/fdc3/api/context/Context.java | 165 ++++++++++++++++++ .../fdc3/api/metadata/ContextMetadata.java | 2 +- .../finos/fdc3/api/types/ContextHandler.java | 2 +- .../finos/fdc3/api/types/IntentHandler.java | 2 +- .../finos/fdc3/api/types/IntentResult.java | 2 +- pom.xml | 2 +- 11 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index b0efca7a..760a5da9 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -23,10 +23,9 @@ - org.finos.fdc3 - fdc3-schema + fdc3-standard ${project.version} @@ -156,7 +155,7 @@ - + fix-context-imports generate-sources @@ -168,8 +167,8 @@ ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/context -c - rm -f Context.java 2>/dev/null; sed -i '' 's/^package org.finos.fdc3.context;$/package org.finos.fdc3.context;\ -import org.finos.fdc3.schema.Context;/' Converter.java || true + rm -f Context.java ContextElement.java 2>/dev/null; for f in *.java; do sed -i '' -e 's/ContextElement/Context/g' "$f"; grep -qE "(private|public) Context |Context get|Context\[\]|ContextFromJsonString" "$f" && sed -i '' 's/^package org.finos.fdc3.context;$/package org.finos.fdc3.context;\ +import org.finos.fdc3.api.context.Context;/' "$f"; done || true 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 index 37bedf03..a5e7423d 100644 --- a/fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java +++ b/fdc3-context/src/main/java/org/finos/fdc3/context/ContextConverter.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.finos.fdc3.schema.Context; +import org.finos.fdc3.api.context.Context; import java.io.IOException; import java.util.HashMap; diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index 76546a4f..db9495e5 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -23,6 +23,13 @@ + + + org.finos.fdc3 + fdc3-standard + ${project.version} + + com.fasterxml.jackson.core @@ -162,6 +169,23 @@ + + + fix-context-imports + generate-sources + + exec + + + /bin/sh + ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema + + -c + rm -f Context.java ContextID.java ContextType.java 2>/dev/null; for f in *.java; do grep -q "private Context " "$f" && sed -i '' 's/^package org.finos.fdc3.schema;$/package org.finos.fdc3.schema;\ +import org.finos.fdc3.api.context.Context;/' "$f"; done || true + + + diff --git a/fdc3-standard/pom.xml b/fdc3-standard/pom.xml index 3042f144..886fa83c 100644 --- a/fdc3-standard/pom.xml +++ b/fdc3-standard/pom.xml @@ -56,17 +56,17 @@ com.fasterxml.jackson.core jackson-core - ${jackson.version} + 2.16.1 com.fasterxml.jackson.core jackson-annotations - ${jackson.version} + 2.16.1 com.fasterxml.jackson.core jackson-databind - ${jackson.version} + 2.16.1 diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index c13ebe85..f939c524 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -18,7 +18,7 @@ import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.channel.PrivateChannel; -import org.finos.fdc3.schema.Context; +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.ImplementationMetadata; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java new file mode 100644 index 00000000..4979799d --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java @@ -0,0 +1,165 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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.api.context; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The base FDC3 Context type. + * + * This implementation uses a Map to store all properties, which allows it to: + * - Preserve all fields during serialization/deserialization (no data loss) + * - Support any context type, including custom ones + * - Be converted to typed context classes using the {@link #as(Class)} method + * + * 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. + */ +public class Context { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final Map properties = new LinkedHashMap<>(); + + public Context() { + } + + public Context(String type) { + setType(type); + } + + public Context(String type, String name) { + setType(type); + setName(name); + } + + public Context(String type, String name, Map id) { + setType(type); + setName(name); + setId(id); + } + + /** + * The type property is the only required part of the FDC3 context data schema. + * The FDC3 API relies on the `type` property being present to route shared context data appropriately. + */ + public String getType() { + return (String) properties.get("type"); + } + + public void setType(String type) { + properties.put("type", type); + } + + /** + * Context data objects may include a name property that can be used for more information, + * or display purposes. + */ + public String getName() { + return (String) properties.get("name"); + } + + public void setName(String name) { + if (name != null) { + properties.put("name", name); + } else { + properties.remove("name"); + } + } + + /** + * 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. + */ + @SuppressWarnings("unchecked") + public Map getId() { + return (Map) properties.get("id"); + } + + public void setId(Map id) { + if (id != null) { + properties.put("id", id); + } else { + properties.remove("id"); + } + } + + /** + * Get a property by name. + */ + public Object get(String name) { + return properties.get(name); + } + + /** + * Set a property by name. + */ + @JsonAnySetter + public void set(String name, Object value) { + if (value != null) { + properties.put(name, value); + } else { + properties.remove(name); + } + } + + /** + * Get all properties as a map. + */ + @JsonAnyGetter + public Map getProperties() { + return properties; + } + + /** + * Convert this context to a typed context class. + * + * Example: + *
+     * Context ctx = ...;
+     * if ("fdc3.instrument".equals(ctx.getType())) {
+     *     Instrument instrument = ctx.as(Instrument.class);
+     * }
+     * 
+ * + * @param clazz the target class + * @param the type of the context + * @return the typed context object + */ + @JsonIgnore + public T as(Class clazz) { + return MAPPER.convertValue(properties, clazz); + } + + @Override + public String toString() { + try { + return MAPPER.writeValueAsString(this); + } catch (Exception e) { + return properties.toString(); + } + } +} + diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index 466e5600..7a20fb93 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -40,6 +40,6 @@ default String getSourceAppId() { /** Get the source instance ID. Convenience method. */ default String getSourceInstanceId() { AppIdentifier source = getSource(); - return source != null ? source.getInstanceId() : null; + return source != null ? source.getInstanceId().orElse(null) : null; } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java index d7a3f5a1..9708bcb2 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextHandler.java @@ -16,7 +16,7 @@ package org.finos.fdc3.api.types; -import org.finos.fdc3.schema.Context; +import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.metadata.ContextMetadata; @FunctionalInterface diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java index 0da951d6..941d7472 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java @@ -19,7 +19,7 @@ import java.util.Optional; import java.util.concurrent.CompletionStage; -import org.finos.fdc3.schema.Context; +import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.metadata.ContextMetadata; /** diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java index c499744f..2deef0e7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java @@ -16,7 +16,7 @@ package org.finos.fdc3.api.types; import org.finos.fdc3.api.channel.Channel; -import org.finos.fdc3.schema.Context; +import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.metadata.IntentResolution; /** diff --git a/pom.xml b/pom.xml index b0f85125..c137e4cf 100644 --- a/pom.xml +++ b/pom.xml @@ -25,9 +25,9 @@ FDC3 Java API http://maven.apache.org + fdc3-standard fdc3-schema fdc3-context - fdc3-standard fdc3-testing fdc3-agent-proxy From 6813b4d7b6fa8839f79a760adf4ea19519d36c11 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 16:39:41 +0000 Subject: [PATCH 12/65] simplifying standards package --- fdc3-standard/pom.xml | 21 ----- .../org/finos/fdc3/api/context/Context.java | 88 +++---------------- .../fdc3/api/utils/JacksonUtilities.java | 86 ------------------ .../finos/fdc3/api/utils/StringUtilities.java | 32 ------- 4 files changed, 12 insertions(+), 215 deletions(-) delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java delete mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java diff --git a/fdc3-standard/pom.xml b/fdc3-standard/pom.xml index 886fa83c..2b7aec91 100644 --- a/fdc3-standard/pom.xml +++ b/fdc3-standard/pom.xml @@ -33,16 +33,10 @@ UTF-8 11 11 - 2.9.8 1.7.25 - - org.jsonschema2pojo - jsonschema2pojo-core - 1.1.1 - org.slf4j slf4j-api @@ -53,21 +47,6 @@ slf4j-jdk14 ${slf4j.version} - - com.fasterxml.jackson.core - jackson-core - 2.16.1 - - - com.fasterxml.jackson.core - jackson-annotations - 2.16.1 - - - com.fasterxml.jackson.core - jackson-databind - 2.16.1 - org.json diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java index 4979799d..71585476 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java @@ -16,20 +16,16 @@ package org.finos.fdc3.api.context; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; /** * The base FDC3 Context type. * - * This implementation uses a Map to store all properties, which allows it to: + * This implementation extends HashMap to store all properties, which allows it to: * - Preserve all fields during serialization/deserialization (no data loss) * - Support any context type, including custom ones + * - Be used directly as a Map * - Be converted to typed context classes using the {@link #as(Class)} method * * The `fdc3.context` type defines the basic contract or "shape" for all data exchanged by @@ -37,11 +33,7 @@ * by more specific type definitions (standardized or custom) to provide the structure and * properties shared by all FDC3 context data types. */ -public class Context { - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private final Map properties = new LinkedHashMap<>(); +public class Context extends HashMap { public Context() { } @@ -66,11 +58,11 @@ public Context(String type, String name, Map id) { * The FDC3 API relies on the `type` property being present to route shared context data appropriately. */ public String getType() { - return (String) properties.get("type"); + return (String) get("type"); } public void setType(String type) { - properties.put("type", type); + put("type", type); } /** @@ -78,14 +70,14 @@ public void setType(String type) { * or display purposes. */ public String getName() { - return (String) properties.get("name"); + return (String) get("name"); } public void setName(String name) { if (name != null) { - properties.put("name", name); + put("name", name); } else { - properties.remove("name"); + remove("name"); } } @@ -95,71 +87,15 @@ public void setName(String name) { */ @SuppressWarnings("unchecked") public Map getId() { - return (Map) properties.get("id"); + return (Map) get("id"); } public void setId(Map id) { if (id != null) { - properties.put("id", id); - } else { - properties.remove("id"); - } - } - - /** - * Get a property by name. - */ - public Object get(String name) { - return properties.get(name); - } - - /** - * Set a property by name. - */ - @JsonAnySetter - public void set(String name, Object value) { - if (value != null) { - properties.put(name, value); + put("id", id); } else { - properties.remove(name); + remove("id"); } } - /** - * Get all properties as a map. - */ - @JsonAnyGetter - public Map getProperties() { - return properties; - } - - /** - * Convert this context to a typed context class. - * - * Example: - *
-     * Context ctx = ...;
-     * if ("fdc3.instrument".equals(ctx.getType())) {
-     *     Instrument instrument = ctx.as(Instrument.class);
-     * }
-     * 
- * - * @param clazz the target class - * @param the type of the context - * @return the typed context object - */ - @JsonIgnore - public T as(Class clazz) { - return MAPPER.convertValue(properties, clazz); - } - - @Override - public String toString() { - try { - return MAPPER.writeValueAsString(this); - } catch (Exception e) { - return properties.toString(); - } - } } - diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java deleted file mode 100644 index 613b9a17..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/JacksonUtilities.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.utils; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.util.List; - -public class JacksonUtilities -{ -private static final ObjectMapper jacksonObjectMapper = new ObjectMapper(); -private JacksonUtilities() { -} - -public static String serializeToString(Object obj) throws Exception { - if (obj == null) { - throw new Exception("null obj specified"); - } else { - try { - return getObjectMapper().writeValueAsString(obj); - } catch (Throwable var2) { - throw new Exception(String.format("error serializaing obj, details: %s - %s", var2.getMessage(), var2.getCause()), var2); - } - } -} - -public static String serializeToPrettyString(Object obj) throws Exception { - if (obj == null) { - throw new Exception("null obj specified"); - } else { - try { - return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj); - } catch (Throwable var2) { - throw new Exception(String.format("error serializaing (pretty) obj, details: %s - %s", var2.getMessage(), var2.getCause()), var2); - } - } -} - -public static T deserializeFromString(String str, Class cl) throws Exception { - if (str == null) { - throw new Exception("null str specified"); - } else if (cl == null) { - throw new Exception("null cl specified"); - } else { - try { - return getObjectMapper().readValue(str, cl); - } catch (Throwable var3) { - throw new Exception(String.format("error deserializing str '%s' for class: %s, details: %s - %s", str, cl.getName(), var3.getMessage(), var3.getCause()), var3); - } - } -} - -public static List deserializeListFromString(String message, Class cl) throws Exception { - if (message == null) { - throw new Exception("null message specified"); - } else if (cl == null) { - throw new Exception("null cl specified"); - } else { - try { - return (List)getObjectMapper().readValue(message, getObjectMapper().getTypeFactory().constructCollectionType(List.class, cl)); - } catch (Throwable var3) { - throw new Exception(String.format("error deserializing list for class: %s from message: %s, details: %s - %s", cl.getName(), message, var3.getMessage(), var3.getCause()), var3); - } - } -} - -public static ObjectMapper getObjectMapper() { - return jacksonObjectMapper; -} - - -} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java deleted file mode 100644 index ea937432..00000000 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/utils/StringUtilities.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.api.utils; - -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"; - } - } -} -} From dc34e90d263fe7747d31713ef7a516ca87352bb5 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 16:40:59 +0000 Subject: [PATCH 13/65] removed unused known issue --- .../org/finos/fdc3/context/ContextRoundTripTest.java | 12 ------------ 1 file changed, 12 deletions(-) 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 index ff4bbdc0..5696c404 100644 --- a/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java +++ b/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java @@ -15,7 +15,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -137,9 +136,6 @@ private void testRoundTrip(String schemaName, String originalJson) throws Except assertJsonContains(originalNode, reserializedNode, schemaName + " (" + type + ")"); } - // Fields that are known to be lost in nested contexts due to ContextElement limitations - private static final Set KNOWN_NESTED_CONTEXT_FIELDS = Set.of("instruments", "range", "style"); - /** * Asserts that all non-null fields in the original JSON are present in the reserialized JSON. */ @@ -150,14 +146,6 @@ private void assertJsonContains(JsonNode original, JsonNode reserialized, String JsonNode reserializedValue = reserialized.get(fieldName); if (originalValue != null && !originalValue.isNull()) { - // Skip known fields that are lost in nested ContextElement serialization - if (reserializedValue == null && context.contains(".context") && - KNOWN_NESTED_CONTEXT_FIELDS.contains(fieldName)) { - System.out.println(" [KNOWN ISSUE] Nested context field '" + fieldName + - "' lost in " + context + " (ContextElement limitation)"); - return; - } - assertNotNull(reserializedValue, "Field '" + fieldName + "' should be present in reserialized JSON for " + context); From bd349a23708d125713c574b40832704b167d006f Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 17:18:17 +0000 Subject: [PATCH 14/65] Starting to use new schema classes in proxy implementation --- fdc3-agent-proxy/pom.xml | 25 +- .../java/org/finos/fdc3/proxy/Messaging.java | 8 + .../fdc3/proxy/apps/DefaultAppSupport.java | 237 ++++++++++------- .../fdc3/proxy/channels/DefaultChannel.java | 68 +++-- .../proxy/channels/DefaultChannelSupport.java | 191 +++++++++----- .../proxy/channels/DefaultPrivateChannel.java | 37 ++- .../heartbeat/DefaultHeartbeatSupport.java | 48 +++- .../proxy/intents/DefaultIntentSupport.java | 248 ++++++++++-------- .../listeners/DefaultContextListener.java | 39 ++- .../listeners/DefaultIntentListener.java | 36 ++- .../listeners/DesktopAgentEventListener.java | 50 +++- .../PrivateChannelEventListener.java | 55 +++- .../proxy/messaging/AbstractMessaging.java | 8 + fdc3-schema/pom.xml | 5 + .../finos/fdc3/schema/SchemaConverter.java | 229 ++++++++++++++++ .../org/finos/fdc3/api/context/Context.java | 15 ++ fdc3-testing/pom.xml | 2 +- 17 files changed, 920 insertions(+), 381 deletions(-) create mode 100644 fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index bafb6d5a..c0407998 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -20,7 +20,6 @@ 11 7.15.0 5.10.1 - 2.16.1 @@ -31,29 +30,19 @@ ${project.version}
- + org.finos.fdc3 - fdc3-testing + fdc3-schema ${project.version} - test - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - + - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} + org.finos.fdc3 + fdc3-testing + ${project.version} + test 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 index dcb17332..c8f1a937 100644 --- 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 @@ -22,6 +22,7 @@ import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.proxy.listeners.RegisterableListener; +import org.finos.fdc3.schema.SchemaConverter; /** * Interface for messaging between the app and the Desktop Agent. @@ -101,5 +102,12 @@ public interface Messaging { * @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(); } 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 index 7e7854ff..9c699362 100644 --- 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 @@ -16,9 +16,10 @@ package org.finos.fdc3.proxy.apps; +import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -35,6 +36,7 @@ import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.proxy.Messaging; import org.finos.fdc3.proxy.util.Logger; +import org.finos.fdc3.schema.*; /** * Default implementation of AppSupport. @@ -52,106 +54,113 @@ public DefaultAppSupport(Messaging messaging, long messageExchangeTimeout, long } @Override - @SuppressWarnings("unchecked") public CompletionStage> findInstances(AppIdentifier app) { - Map request = new HashMap<>(); - request.put("type", "findInstancesRequest"); - request.put("meta", messaging.createMeta()); - - Map payload = new HashMap<>(); - Map appMap = new HashMap<>(); - appMap.put("appId", app.getAppId()); - app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); - payload.put("app", appMap); - request.put("payload", payload); - - return messaging.>exchange(request, "findInstancesResponse", messageExchangeTimeout) + // Build typed request + FindInstancesRequest request = new FindInstancesRequest(); + request.setType(FindInstancesRequestType.FIND_INSTANCES_REQUEST); + request.setMeta(createMeta()); + + FindInstancesRequestPayload payload = new FindInstancesRequestPayload(); + payload.setApp(toSchemaAppIdentifier(app)); + request.setPayload(payload); + + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "findInstancesResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - List> identifiers = (List>) responsePayload.get("appIdentifiers"); - - if (identifiers == null) { + FindInstancesResponse typedResponse = messaging.getConverter() + .convertValue(response, FindInstancesResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIdentifiers() == null) { return new ArrayList<>(); } - return identifiers.stream() - .map(id -> createAppIdentifier(id)) + return Arrays.stream(typedResponse.getPayload().getAppIdentifiers()) + .map(this::toApiAppIdentifier) .collect(Collectors.toList()); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage getAppMetadata(AppIdentifier app) { - Map request = new HashMap<>(); - request.put("type", "getAppMetadataRequest"); - request.put("meta", messaging.createMeta()); - - Map payload = new HashMap<>(); - Map appMap = new HashMap<>(); - appMap.put("appId", app.getAppId()); - app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); - payload.put("app", appMap); - request.put("payload", payload); - - return messaging.>exchange(request, "getAppMetadataResponse", messageExchangeTimeout) + // Build typed request + GetAppMetadataRequest request = new GetAppMetadataRequest(); + request.setType(GetAppMetadataRequestType.GET_APP_METADATA_REQUEST); + request.setMeta(createMeta()); + + GetAppMetadataRequestPayload payload = new GetAppMetadataRequestPayload(); + payload.setApp(toSchemaAppIdentifier(app)); + request.setPayload(payload); + + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getAppMetadataResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map appMetadataMap = (Map) responsePayload.get("appMetadata"); - - if (appMetadataMap == null) { + GetAppMetadataResponse typedResponse = messaging.getConverter() + .convertValue(response, GetAppMetadataResponse.class); + + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppMetadata() == null) { throw new RuntimeException(ResolveError.TargetAppUnavailable.toString()); } - return parseAppMetadata(appMetadataMap); + return toApiAppMetadata(typedResponse.getPayload().getAppMetadata()); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage open(AppIdentifier app, Context context) { - Map request = new HashMap<>(); - request.put("type", "openRequest"); - request.put("meta", messaging.createMeta()); - - Map payload = new HashMap<>(); - Map appMap = new HashMap<>(); - appMap.put("appId", app.getAppId()); - app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); - payload.put("app", appMap); + // Build typed request + OpenRequest request = new OpenRequest(); + request.setType(OpenRequestType.OPEN_REQUEST); + request.setMeta(createMeta()); + + OpenRequestPayload payload = new OpenRequestPayload(); + payload.setApp(toSchemaAppIdentifier(app)); if (context != null) { - payload.put("context", context.toMap()); + payload.setContext(context); } - request.put("payload", payload); + request.setPayload(payload); - return messaging.>exchange(request, "openResponse", appLaunchTimeout) - .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map appIdentifierMap = (Map) responsePayload.get("appIdentifier"); + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); - if (appIdentifierMap == null) { + 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 createAppIdentifier(appIdentifierMap); + return toApiAppIdentifier(typedResponse.getPayload().getAppIdentifier()); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage getImplementationMetadata() { - Map request = new HashMap<>(); - request.put("type", "getInfoRequest"); - request.put("meta", messaging.createMeta()); - request.put("payload", new HashMap<>()); + // Build typed request + GetInfoRequest request = new GetInfoRequest(); + request.setType(GetInfoRequestType.GET_INFO_REQUEST); + request.setMeta(createMeta()); + request.setPayload(new GetInfoRequestPayload()); - return messaging.>exchange(request, "getInfoResponse", messageExchangeTimeout) - .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map implMetadata = (Map) responsePayload.get("implementationMetadata"); + // Convert to Map for messaging + Map requestMap = messaging.getConverter().toMap(request); - if (implMetadata != null) { - return parseImplementationMetadata(implMetadata); + return messaging.>exchange(requestMap, "getInfoResponse", messageExchangeTimeout) + .thenApply(response -> { + GetInfoResponse typedResponse = messaging.getConverter() + .convertValue(response, GetInfoResponse.class); + + if (typedResponse.getPayload() != null && + typedResponse.getPayload().getImplementationMetadata() != null) { + return toApiImplementationMetadata(typedResponse.getPayload().getImplementationMetadata()); } else { Logger.error("Invalid response from Desktop Agent to getInfo!"); return createUnknownImplementationMetadata(); @@ -159,9 +168,34 @@ public CompletionStage getImplementationMetadata() { }); } - private AppIdentifier createAppIdentifier(Map idMap) { - String appId = (String) idMap.get("appId"); - String instanceId = (String) idMap.get("instanceId"); + // ============ Conversion helpers ============ + + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + // Set source from messaging's app identifier + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } + + private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { + org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); + schemaApp.setAppID(app.getAppId()); + app.getInstanceId().ifPresent(schemaApp::setInstanceID); + return schemaApp; + } + + private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { + String appId = schemaApp.getAppID(); + String instanceId = schemaApp.getInstanceID(); return new AppIdentifier() { @Override public String getAppId() { @@ -175,12 +209,31 @@ public Optional getInstanceId() { }; } - private AppMetadata parseAppMetadata(Map appMap) { - String appId = (String) appMap.get("appId"); - String instanceId = (String) appMap.get("instanceId"); - String name = (String) appMap.get("name"); - String title = (String) appMap.get("title"); - String description = (String) appMap.get("description"); + private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppMetadata schemaApp) { + String appId = schemaApp.getAppID(); + String instanceId = schemaApp.getInstanceID(); + return new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + }; + } + + private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaMetadata) { + String appId = schemaMetadata.getAppID(); + String instanceId = schemaMetadata.getInstanceID(); + String name = schemaMetadata.getName(); + String title = schemaMetadata.getTitle(); + String description = schemaMetadata.getDescription(); + String version = schemaMetadata.getVersion(); + String tooltip = schemaMetadata.getTooltip(); + String resultType = schemaMetadata.getResultType(); return new AppMetadata() { @Override @@ -200,7 +253,7 @@ public Optional getName() { @Override public Optional getVersion() { - return Optional.empty(); + return Optional.ofNullable(version); } @Override @@ -215,7 +268,7 @@ public Optional getTitle() { @Override public Optional getTooltip() { - return Optional.empty(); + return Optional.ofNullable(tooltip); } @Override @@ -235,20 +288,19 @@ public Optional> getScreenshots() { @Override public Optional getResultType() { - return Optional.empty(); + return Optional.ofNullable(resultType); } }; } - @SuppressWarnings("unchecked") - private ImplementationMetadata parseImplementationMetadata(Map implMap) { - String fdc3Version = (String) implMap.get("fdc3Version"); - String provider = (String) implMap.get("provider"); - String providerVersion = (String) implMap.get("providerVersion"); - Map appMetadataMap = (Map) implMap.get("appMetadata"); - Map optionalFeaturesMap = (Map) implMap.get("optionalFeatures"); + private ImplementationMetadata toApiImplementationMetadata(org.finos.fdc3.schema.ImplementationMetadata schemaMetadata) { + String fdc3Version = schemaMetadata.getFdc3Version(); + String provider = schemaMetadata.getProvider(); + String providerVersion = schemaMetadata.getProviderVersion(); + org.finos.fdc3.schema.AppMetadata schemaAppMetadata = schemaMetadata.getAppMetadata(); + org.finos.fdc3.schema.OptionalFeatures schemaOptionalFeatures = schemaMetadata.getOptionalFeatures(); - AppMetadata appMetadata = appMetadataMap != null ? parseAppMetadata(appMetadataMap) : null; + AppMetadata appMetadata = schemaAppMetadata != null ? toApiAppMetadata(schemaAppMetadata) : null; return new ImplementationMetadata() { @Override @@ -273,26 +325,23 @@ public AppMetadata getAppMetadata() { @Override public OptionalFeatures getOptionalFeatures() { - if (optionalFeaturesMap == null) { + if (schemaOptionalFeatures == null) { return null; } return new OptionalFeatures() { @Override public boolean isOriginatingAppMetadata() { - Boolean val = (Boolean) optionalFeaturesMap.get("OriginatingAppMetadata"); - return val != null && val; + return schemaOptionalFeatures.getOriginatingAppMetadata(); } @Override public boolean isUserChannelMembershipAPIs() { - Boolean val = (Boolean) optionalFeaturesMap.get("UserChannelMembershipAPIs"); - return val != null && val; + return schemaOptionalFeatures.getUserChannelMembershipAPIs(); } @Override public boolean isDesktopAgentBridging() { - Boolean val = (Boolean) optionalFeaturesMap.get("DesktopAgentBridging"); - return val != null && val; + return schemaOptionalFeatures.getDesktopAgentBridging(); } }; } 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 index 53e0bba2..a87861b5 100644 --- 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 @@ -16,7 +16,7 @@ package org.finos.fdc3.proxy.channels; -import java.util.HashMap; +import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -24,10 +24,12 @@ 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.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; import org.finos.fdc3.proxy.listeners.DefaultContextListener; +import org.finos.fdc3.schema.*; /** * Default implementation of a Channel. @@ -69,18 +71,19 @@ public Optional displayMetadata() { } @Override - @SuppressWarnings("unchecked") public CompletionStage broadcast(Context context) { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "broadcastRequest"); + BroadcastRequest request = new BroadcastRequest(); + request.setType(BroadcastRequestType.BROADCAST_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("channelId", id); - payload.put("context", context.toMap()); - request.put("payload", payload); + BroadcastRequestPayload payload = new BroadcastRequestPayload(); + payload.setChannelID(id); + payload.setContext(context); + request.setPayload(payload); - return messaging.>exchange(request, "broadcastResponse", messageExchangeTimeout) + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "broadcastResponse", messageExchangeTimeout) .thenApply(response -> null); } @@ -90,23 +93,26 @@ public CompletionStage> getCurrentContext() { } @Override - @SuppressWarnings("unchecked") public CompletionStage> getCurrentContext(String contextType) { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "getCurrentContextRequest"); + GetCurrentContextRequest request = new GetCurrentContextRequest(); + request.setType(GetCurrentContextRequestType.GET_CURRENT_CONTEXT_REQUEST); + request.setMeta(createMeta()); + + GetCurrentContextRequestPayload payload = new GetCurrentContextRequestPayload(); + payload.setChannelID(id); + payload.setContextType(contextType); + request.setPayload(payload); - Map payload = new HashMap<>(); - payload.put("channelId", id); - payload.put("contextType", contextType); - request.put("payload", payload); + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "getCurrentContextResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "getCurrentContextResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map contextMap = (Map) responsePayload.get("context"); - if (contextMap != null) { - return Optional.of(Context.fromMap(contextMap)); + GetCurrentContextResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentContextResponse.class); + + if (typedResponse.getPayload() != null && + typedResponse.getPayload().getContext() != null) { + return Optional.of(typedResponse.getPayload().getContext()); } return Optional.empty(); }); @@ -127,5 +133,19 @@ protected CompletionStage addContextListenerInner(String contextType, ); return listener.register().thenApply(v -> listener); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } +} 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 index 81651357..1f4a439b 100644 --- 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 @@ -16,8 +16,9 @@ package org.finos.fdc3.proxy.channels; +import java.time.OffsetDateTime; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -28,12 +29,14 @@ import org.finos.fdc3.api.channel.PrivateChannel; import org.finos.fdc3.api.errors.ChannelError; 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.EventHandler; import org.finos.fdc3.api.types.Listener; 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.*; /** * Default implementation of ChannelSupport. @@ -106,29 +109,29 @@ public CompletionStage addEventListener(EventHandler handler, String t } @Override - @SuppressWarnings("unchecked") public CompletionStage getUserChannel() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "getCurrentChannelRequest"); - request.put("payload", new HashMap<>()); + GetCurrentChannelRequest request = new GetCurrentChannelRequest(); + request.setType(GetCurrentChannelRequestType.GET_CURRENT_CHANNEL_REQUEST); + request.setMeta(createMeta()); + request.setPayload(new GetCurrentChannelRequestPayload()); - return messaging.>exchange(request, "getCurrentChannelResponse", messageExchangeTimeout) + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "getCurrentChannelResponse", messageExchangeTimeout) .thenApply(response -> { - Map payload = (Map) response.get("payload"); - Map channel = (Map) payload.get("channel"); + GetCurrentChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentChannelResponse.class); - if (channel == null) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getChannel() == null) { return null; } - String id = (String) channel.get("id"); - Map displayMetadataMap = (Map) channel.get("displayMetadata"); - DisplayMetadata displayMetadata = displayMetadataMap != null - ? DisplayMetadata.fromMap(displayMetadataMap) - : null; + org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); + DisplayMetadata displayMetadata = toApiDisplayMetadata(schemaChannel.getDisplayMetadata()); - return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.User, displayMetadata); + return new DefaultChannel(messaging, messageExchangeTimeout, + schemaChannel.getID(), Channel.Type.User, displayMetadata); }); } @@ -140,32 +143,30 @@ private CompletionStage> getUserChannelsCached() { } @Override - @SuppressWarnings("unchecked") public CompletionStage> getUserChannels() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "getUserChannelsRequest"); - request.put("payload", new HashMap<>()); + GetUserChannelsRequest request = new GetUserChannelsRequest(); + request.setType(GetUserChannelsRequestType.GET_USER_CHANNELS_REQUEST); + request.setMeta(createMeta()); + request.setPayload(new GetUserChannelsRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "getUserChannelsResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "getUserChannelsResponse", messageExchangeTimeout) .thenApply(response -> { - Map payload = (Map) response.get("payload"); - List> channelList = (List>) payload.get("userChannels"); + GetUserChannelsResponse typedResponse = messaging.getConverter() + .convertValue(response, GetUserChannelsResponse.class); - if (channelList == null) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getUserChannels() == null) { userChannels = new ArrayList<>(); return userChannels; } - userChannels = channelList.stream() + userChannels = Arrays.stream(typedResponse.getPayload().getUserChannels()) .map(c -> { - String id = (String) c.get("id"); - Map displayMetadataMap = (Map) c.get("displayMetadata"); - DisplayMetadata displayMetadata = displayMetadataMap != null - ? DisplayMetadata.fromMap(displayMetadataMap) - : null; + DisplayMetadata displayMetadata = toApiDisplayMetadata(c.getDisplayMetadata()); return (Channel) new DefaultChannel( - messaging, messageExchangeTimeout, id, Channel.Type.User, displayMetadata); + messaging, messageExchangeTimeout, c.getID(), Channel.Type.User, displayMetadata); }) .collect(Collectors.toList()); @@ -174,65 +175,68 @@ public CompletionStage> getUserChannels() { } @Override - @SuppressWarnings("unchecked") public CompletionStage getOrCreate(String id) { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "getOrCreateChannelRequest"); + GetOrCreateChannelRequest request = new GetOrCreateChannelRequest(); + request.setType(GetOrCreateChannelRequestType.GET_OR_CREATE_CHANNEL_REQUEST); + request.setMeta(createMeta()); + + GetOrCreateChannelRequestPayload payload = new GetOrCreateChannelRequestPayload(); + payload.setChannelID(id); + request.setPayload(payload); - Map payload = new HashMap<>(); - payload.put("channelId", id); - request.put("payload", payload); + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "getOrCreateChannelResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "getOrCreateChannelResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map channel = (Map) responsePayload.get("channel"); + GetOrCreateChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, GetOrCreateChannelResponse.class); - if (channel == null) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getChannel() == null) { throw new RuntimeException(ChannelError.CreationFailed.toString()); } - Map displayMetadataMap = (Map) channel.get("displayMetadata"); - DisplayMetadata displayMetadata = displayMetadataMap != null - ? DisplayMetadata.fromMap(displayMetadataMap) - : null; + org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); + DisplayMetadata displayMetadata = toApiDisplayMetadata(schemaChannel.getDisplayMetadata()); return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, displayMetadata); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage createPrivateChannel() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "createPrivateChannelRequest"); - request.put("payload", new HashMap<>()); + CreatePrivateChannelRequest request = new CreatePrivateChannelRequest(); + request.setType(CreatePrivateChannelRequestType.CREATE_PRIVATE_CHANNEL_REQUEST); + request.setMeta(createMeta()); + request.setPayload(new CreatePrivateChannelRequestPayload()); - return messaging.>exchange(request, "createPrivateChannelResponse", messageExchangeTimeout) + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "createPrivateChannelResponse", messageExchangeTimeout) .thenApply(response -> { - Map payload = (Map) response.get("payload"); - Map channel = (Map) payload.get("privateChannel"); + CreatePrivateChannelResponse typedResponse = messaging.getConverter() + .convertValue(response, CreatePrivateChannelResponse.class); - if (channel == null) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getPrivateChannel() == null) { throw new RuntimeException(ChannelError.CreationFailed.toString()); } - String id = (String) channel.get("id"); + String id = typedResponse.getPayload().getPrivateChannel().getID(); return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage leaveUserChannel() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "leaveCurrentChannelRequest"); - request.put("payload", new HashMap<>()); + LeaveCurrentChannelRequest request = new LeaveCurrentChannelRequest(); + request.setType(LeaveCurrentChannelRequestType.LEAVE_CURRENT_CHANNEL_REQUEST); + request.setMeta(createMeta()); + request.setPayload(new LeaveCurrentChannelRequestPayload()); + + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "leaveCurrentChannelResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "leaveCurrentChannelResponse", messageExchangeTimeout) .thenCompose(response -> { currentChannel = null; return getUserChannelsCached().thenAccept(channels -> { @@ -242,17 +246,18 @@ public CompletionStage leaveUserChannel() { } @Override - @SuppressWarnings("unchecked") public CompletionStage joinUserChannel(String id) { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "joinUserChannelRequest"); + JoinUserChannelRequest request = new JoinUserChannelRequest(); + request.setType(JoinUserChannelRequestType.JOIN_USER_CHANNEL_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("channelId", id); - request.put("payload", payload); + JoinUserChannelRequestPayload payload = new JoinUserChannelRequestPayload(); + payload.setChannelID(id); + request.setPayload(payload); - return messaging.>exchange(request, "joinUserChannelResponse", messageExchangeTimeout) + Map requestMap = messaging.getConverter().toMap(request); + + return messaging.>exchange(requestMap, "joinUserChannelResponse", messageExchangeTimeout) .thenCompose(response -> getUserChannelsCached()) .thenAccept(channels -> { currentChannel = channels.stream() @@ -278,5 +283,47 @@ public CompletionStage addContextListener(ContextHandler handler, Stri Channel getCurrentChannelInternal() { return currentChannel; } -} + // ============ Helper methods ============ + + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } + + private DisplayMetadata toApiDisplayMetadata(org.finos.fdc3.schema.DisplayMetadata schemaDisplayMetadata) { + if (schemaDisplayMetadata == null) { + return null; + } + String name = schemaDisplayMetadata.getName(); + String color = schemaDisplayMetadata.getColor(); + String glyph = schemaDisplayMetadata.getGlyph(); + + return new DisplayMetadata() { + @Override + public java.util.Optional getName() { + return java.util.Optional.ofNullable(name); + } + + @Override + public java.util.Optional getColor() { + return java.util.Optional.ofNullable(color); + } + + @Override + public java.util.Optional getGlyph() { + return java.util.Optional.ofNullable(glyph); + } + }; + } +} 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 index b19fa442..cf8d1e40 100644 --- 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 @@ -16,7 +16,7 @@ package org.finos.fdc3.proxy.channels; -import java.util.HashMap; +import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -24,11 +24,13 @@ import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.channel.PrivateChannel; +import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; import org.finos.fdc3.proxy.listeners.DefaultContextListener; import org.finos.fdc3.proxy.listeners.PrivateChannelEventListener; +import org.finos.fdc3.schema.*; /** * Default implementation of a PrivateChannel. @@ -67,18 +69,19 @@ messaging, messageExchangeTimeout, getId(), "disconnect", } @Override - @SuppressWarnings("unchecked") public void disconnect() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "privateChannelDisconnectRequest"); + PrivateChannelDisconnectRequest request = new PrivateChannelDisconnectRequest(); + request.setType(PrivateChannelDisconnectRequestType.PRIVATE_CHANNEL_DISCONNECT_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("channelId", getId()); - request.put("payload", payload); + PrivateChannelDisconnectRequestPayload payload = new PrivateChannelDisconnectRequestPayload(); + payload.setChannelID(getId()); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); try { - messaging.>exchange(request, "privateChannelDisconnectResponse", messageExchangeTimeout) + messaging.>exchange(requestMap, "privateChannelDisconnectResponse", messageExchangeTimeout) .toCompletableFuture().get(); } catch (Exception e) { throw new RuntimeException("Failed to disconnect private channel", e); @@ -96,5 +99,19 @@ protected CompletionStage addContextListenerInner(String contextType, ); return listener.register().thenApply(v -> listener); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } +} 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 index a7cd1c11..0089cb5a 100644 --- 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 @@ -16,17 +16,19 @@ package org.finos.fdc3.proxy.heartbeat; +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 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.*; /** * Default implementation of HeartbeatSupport. @@ -69,7 +71,7 @@ public boolean filter(Map message) { @Override public void action(Map message) { Map payload = (Map) message.get("payload"); - String timestamp = (String) payload.get("timestamp"); + String timestamp = payload != null ? (String) payload.get("timestamp") : null; Logger.debug("Received heartbeat at {}", timestamp); // Respond to heartbeat @@ -96,19 +98,25 @@ public void unsubscribe() { private void respondToHeartbeat(Map heartbeatEvent) { try { Map meta = (Map) heartbeatEvent.get("meta"); - String requestUuid = (String) meta.get("requestUuid"); + String eventUuid = meta != null ? (String) meta.get("eventUuid") : null; + + HeartbeatAcknowledgementRequest request = new HeartbeatAcknowledgementRequest(); + request.setType(HeartbeatAcknowledgementRequestType.HEARTBEAT_ACKNOWLEDGEMENT_REQUEST); + + AddContextListenerRequestMeta requestMeta = createMeta(); + // Set the requestUuid to match the eventUuid for correlation + if (eventUuid != null) { + requestMeta.setRequestUUID(eventUuid); + } + request.setMeta(requestMeta); - java.util.Map response = new java.util.HashMap<>(); - java.util.Map responseMeta = messaging.createMeta(); - responseMeta.put("requestUuid", requestUuid); - response.put("meta", responseMeta); - response.put("type", "heartbeatAcknowledgementRequest"); + HeartbeatAcknowledgementRequestPayload payload = new HeartbeatAcknowledgementRequestPayload(); + payload.setHeartbeatEventUUID(eventUuid); + request.setPayload(payload); - java.util.Map payload = new java.util.HashMap<>(); - payload.put("heartbeatEventUuid", requestUuid); - response.put("payload", payload); + Map requestMap = messaging.getConverter().toMap(request); - messaging.post(response); + messaging.post(requestMap); } catch (Exception e) { Logger.error("Failed to respond to heartbeat", e); } @@ -125,5 +133,19 @@ public CompletionStage disconnect() { scheduler.shutdown(); return CompletableFuture.completedFuture(null); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } +} 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 index 0121eea6..9d40f4dd 100644 --- 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 @@ -16,8 +16,9 @@ package org.finos.fdc3.proxy.intents; +import java.time.OffsetDateTime; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -37,6 +38,7 @@ import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; import org.finos.fdc3.proxy.listeners.DefaultIntentListener; +import org.finos.fdc3.schema.*; /** * Default implementation of IntentSupport. @@ -60,32 +62,34 @@ public DefaultIntentSupport( } @Override - @SuppressWarnings("unchecked") public CompletionStage findIntent(String intent, Context context, String resultType) { - Map request = new HashMap<>(); - request.put("type", "findIntentRequest"); - request.put("meta", messaging.createMeta()); + FindIntentRequest request = new FindIntentRequest(); + request.setType(FindIntentRequestType.FIND_INTENT_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("intent", intent); + FindIntentRequestPayload payload = new FindIntentRequestPayload(); + payload.setIntent(intent); if (context != null) { - payload.put("context", context.toMap()); + payload.setContext(context); } if (resultType != null) { - payload.put("resultType", resultType); + payload.setResultType(resultType); } - request.put("payload", payload); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "findIntentResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "findIntentResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - Map appIntentMap = (Map) responsePayload.get("appIntent"); + FindIntentResponse typedResponse = messaging.getConverter() + .convertValue(response, FindIntentResponse.class); - if (appIntentMap == null) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIntent() == null) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - AppIntent appIntent = parseAppIntent(appIntentMap); + AppIntent appIntent = toApiAppIntent(typedResponse.getPayload().getAppIntent()); if (appIntent.getApps().isEmpty()) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } @@ -95,66 +99,73 @@ public CompletionStage findIntent(String intent, Context context, Str } @Override - @SuppressWarnings("unchecked") public CompletionStage> findIntentsByContext(Context context) { - Map request = new HashMap<>(); - request.put("type", "findIntentsByContextRequest"); - request.put("meta", messaging.createMeta()); + FindIntentsByContextRequest request = new FindIntentsByContextRequest(); + request.setType(FindIntentsByContextRequestType.FIND_INTENTS_BY_CONTEXT_REQUEST); + request.setMeta(createMeta()); + + FindIntentsByContextRequestPayload payload = new FindIntentsByContextRequestPayload(); + payload.setContext(context); + request.setPayload(payload); - Map payload = new HashMap<>(); - payload.put("context", context.toMap()); - request.put("payload", payload); + Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(request, "findIntentsByContextResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "findIntentsByContextResponse", messageExchangeTimeout) .thenApply(response -> { - Map responsePayload = (Map) response.get("payload"); - List> appIntentsList = (List>) responsePayload.get("appIntents"); + FindIntentsByContextResponse typedResponse = messaging.getConverter() + .convertValue(response, FindIntentsByContextResponse.class); - if (appIntentsList == null || appIntentsList.isEmpty()) { + if (typedResponse.getPayload() == null || + typedResponse.getPayload().getAppIntents() == null || + typedResponse.getPayload().getAppIntents().length == 0) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - return appIntentsList.stream() - .map(this::parseAppIntent) + return Arrays.stream(typedResponse.getPayload().getAppIntents()) + .map(this::toApiAppIntent) .collect(Collectors.toList()); }); } @Override - @SuppressWarnings("unchecked") public CompletionStage raiseIntent(String intent, Context context, AppIdentifier app) { - Map meta = messaging.createMeta(); - String requestUuid = (String) meta.get("requestUuid"); + AddContextListenerRequestMeta meta = createMeta(); + String requestUuid = meta.getRequestUUID(); - Map request = new HashMap<>(); - request.put("type", "raiseIntentRequest"); - request.put("meta", meta); + RaiseIntentRequest request = new RaiseIntentRequest(); + request.setType(RaiseIntentRequestType.RAISE_INTENT_REQUEST); + request.setMeta(meta); - Map payload = new HashMap<>(); - payload.put("intent", intent); - payload.put("context", context.toMap()); + RaiseIntentRequestPayload payload = new RaiseIntentRequestPayload(); + payload.setIntent(intent); + payload.setContext(context); if (app != null) { - Map appMap = new HashMap<>(); - appMap.put("appId", app.getAppId()); - app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); - payload.put("app", appMap); + payload.setApp(toSchemaAppIdentifier(app)); } - request.put("payload", payload); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); CompletionStage resultPromise = createResultPromise(requestUuid); - return messaging.>exchange(request, "raiseIntentResponse", appLaunchTimeout) + return messaging.>exchange(requestMap, "raiseIntentResponse", appLaunchTimeout) .thenCompose(response -> { - Map responsePayload = (Map) response.get("payload"); - Map appIntentMap = (Map) responsePayload.get("appIntent"); - Map intentResolutionMap = (Map) responsePayload.get("intentResolution"); + RaiseIntentResponse typedResponse = messaging.getConverter() + .convertValue(response, RaiseIntentResponse.class); - if (appIntentMap == null && intentResolutionMap == null) { + if (typedResponse.getPayload() == null) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - if (appIntentMap != null) { - AppIntent appIntent = parseAppIntent(appIntentMap); + org.finos.fdc3.schema.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) { + AppIntent appIntent = toApiAppIntent(schemaAppIntent); return intentResolver.chooseIntent(List.of(appIntent), context) .thenCompose(choice -> { if (choice == null) { @@ -163,9 +174,8 @@ public CompletionStage raiseIntent(String intent, Context cont return raiseIntent(intent, context, choice.getAppId()); }); } else { - Map sourceMap = (Map) intentResolutionMap.get("source"); - String resolvedIntent = (String) intentResolutionMap.get("intent"); - AppIdentifier source = createAppIdentifier(sourceMap); + AppIdentifier source = toApiAppIdentifier(schemaIntentResolution.getSource()); + String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); @@ -174,40 +184,44 @@ public CompletionStage raiseIntent(String intent, Context cont } @Override - @SuppressWarnings("unchecked") public CompletionStage raiseIntentForContext(Context context, AppIdentifier app) { - Map meta = messaging.createMeta(); - String requestUuid = (String) meta.get("requestUuid"); + AddContextListenerRequestMeta meta = createMeta(); + String requestUuid = meta.getRequestUUID(); - Map request = new HashMap<>(); - request.put("type", "raiseIntentForContextRequest"); - request.put("meta", meta); + RaiseIntentForContextRequest request = new RaiseIntentForContextRequest(); + request.setType(RaiseIntentForContextRequestType.RAISE_INTENT_FOR_CONTEXT_REQUEST); + request.setMeta(meta); - Map payload = new HashMap<>(); - payload.put("context", context.toMap()); + RaiseIntentForContextRequestPayload payload = new RaiseIntentForContextRequestPayload(); + payload.setContext(context); if (app != null) { - Map appMap = new HashMap<>(); - appMap.put("appId", app.getAppId()); - app.getInstanceId().ifPresent(id -> appMap.put("instanceId", id)); - payload.put("app", appMap); + payload.setApp(toSchemaAppIdentifier(app)); } - request.put("payload", payload); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); CompletionStage resultPromise = createResultPromise(requestUuid); - return messaging.>exchange(request, "raiseIntentForContextResponse", appLaunchTimeout) + return messaging.>exchange(requestMap, "raiseIntentForContextResponse", appLaunchTimeout) .thenCompose(response -> { - Map responsePayload = (Map) response.get("payload"); - List> appIntentsList = (List>) responsePayload.get("appIntents"); - Map intentResolutionMap = (Map) responsePayload.get("intentResolution"); + RaiseIntentForContextResponse typedResponse = messaging.getConverter() + .convertValue(response, RaiseIntentForContextResponse.class); - if ((appIntentsList == null || appIntentsList.isEmpty()) && intentResolutionMap == null) { + if (typedResponse.getPayload() == null) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - if (appIntentsList != null && !appIntentsList.isEmpty()) { - List appIntents = appIntentsList.stream() - .map(this::parseAppIntent) + org.finos.fdc3.schema.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) { + List appIntents = Arrays.stream(schemaAppIntents) + .map(this::toApiAppIntent) .collect(Collectors.toList()); return intentResolver.chooseIntent(appIntents, context) @@ -218,9 +232,8 @@ public CompletionStage raiseIntentForContext(Context context, return raiseIntent(choice.getIntent(), context, choice.getAppId()); }); } else { - Map sourceMap = (Map) intentResolutionMap.get("source"); - String resolvedIntent = (String) intentResolutionMap.get("intent"); - AppIdentifier source = createAppIdentifier(sourceMap); + AppIdentifier source = toApiAppIdentifier(schemaIntentResolution.getSource()); + String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); @@ -234,6 +247,46 @@ public CompletionStage addIntentListener(String intent, IntentHandler return listener.register().thenApply(v -> listener); } + // ============ Helper methods ============ + + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } + + private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { + org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); + schemaApp.setAppID(app.getAppId()); + app.getInstanceId().ifPresent(schemaApp::setInstanceID); + return schemaApp; + } + + private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { + String appId = schemaApp.getAppID(); + String instanceId = schemaApp.getInstanceID(); + return new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return Optional.ofNullable(instanceId); + } + }; + } + @SuppressWarnings("unchecked") private CompletionStage createResultPromise(String requestUuid) { return messaging.>waitFor( @@ -251,29 +304,12 @@ private CompletionStage createResultPromise(String requestUuid) { }); } - private AppIdentifier createAppIdentifier(Map sourceMap) { - String appId = (String) sourceMap.get("appId"); - String instanceId = (String) sourceMap.get("instanceId"); - return new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - }; - } - - @SuppressWarnings("unchecked") - private AppIntent parseAppIntent(Map appIntentMap) { - Map intentMap = (Map) appIntentMap.get("intent"); - List> appsList = (List>) appIntentMap.get("apps"); + private AppIntent toApiAppIntent(org.finos.fdc3.schema.AppIntent schemaAppIntent) { + org.finos.fdc3.schema.IntentMetadata schemaIntent = schemaAppIntent.getIntent(); + org.finos.fdc3.schema.AppMetadata[] schemaApps = schemaAppIntent.getApps(); - String intentName = (String) intentMap.get("name"); - String displayName = (String) intentMap.get("displayName"); + String intentName = schemaIntent.getName(); + String displayName = schemaIntent.getDisplayName(); IntentMetadata intent = new IntentMetadata() { @Override @@ -287,8 +323,8 @@ public String getDisplayName() { } }; - List apps = appsList.stream() - .map(this::parseAppMetadata) + List apps = Arrays.stream(schemaApps) + .map(this::toApiAppMetadata) .collect(Collectors.toList()); return new AppIntent() { @@ -304,12 +340,12 @@ public List getApps() { }; } - private AppMetadata parseAppMetadata(Map appMap) { - String appId = (String) appMap.get("appId"); - String instanceId = (String) appMap.get("instanceId"); - String name = (String) appMap.get("name"); - String title = (String) appMap.get("title"); - String description = (String) appMap.get("description"); + private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaApp) { + String appId = schemaApp.getAppID(); + String instanceId = schemaApp.getInstanceID(); + String name = schemaApp.getName(); + String title = schemaApp.getTitle(); + String description = schemaApp.getDescription(); return new AppMetadata() { @Override 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 index 442097ae..0151cf05 100644 --- 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 @@ -16,15 +16,17 @@ package org.finos.fdc3.proxy.listeners; -import java.util.HashMap; +import java.time.OffsetDateTime; 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.api.types.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.*; /** * Default implementation of a context listener. @@ -107,19 +109,20 @@ public void action(Map message) { @Override public CompletionStage register() { - // Register with messaging to receive broadcasts - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "addContextListenerRequest"); + AddContextListenerRequest request = new AddContextListenerRequest(); + request.setType(AddContextListenerRequestType.ADD_CONTEXT_LISTENER_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("channelId", channelId); - payload.put("contextType", contextType); - request.put("payload", payload); + AddContextListenerRequestPayload payload = new AddContextListenerRequestPayload(); + payload.setChannelID(channelId); + payload.setContextType(contextType); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); messaging.register(this); - return messaging.>exchange(request, "addContextListenerResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "addContextListenerResponse", messageExchangeTimeout) .thenApply(response -> null); } @@ -132,5 +135,19 @@ public CompletionStage unsubscribeAsync() { messaging.unregister(id); return CompletableFuture.completedFuture(null); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } +} 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 index 1c77056a..a622879d 100644 --- 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 @@ -16,9 +16,8 @@ package org.finos.fdc3.proxy.listeners; -import java.util.HashMap; +import java.time.OffsetDateTime; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.finos.fdc3.api.context.Context; @@ -27,6 +26,7 @@ import org.finos.fdc3.api.types.IntentHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.*; /** * Default implementation of an intent listener. @@ -111,17 +111,19 @@ public java.util.Optional getInstanceId() { @Override public CompletionStage register() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "addIntentListenerRequest"); + AddIntentListenerRequest request = new AddIntentListenerRequest(); + request.setType(AddIntentListenerRequestType.ADD_INTENT_LISTENER_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("intent", intent); - request.put("payload", payload); + AddIntentListenerRequestPayload payload = new AddIntentListenerRequestPayload(); + payload.setIntent(intent); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); messaging.register(this); - return messaging.>exchange(request, "addIntentListenerResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "addIntentListenerResponse", messageExchangeTimeout) .thenApply(response -> null); } @@ -129,5 +131,19 @@ public CompletionStage register() { public void unsubscribe() { messaging.unregister(id); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } +} 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 index f945555f..593a37c0 100644 --- 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 @@ -16,15 +16,16 @@ package org.finos.fdc3.proxy.listeners; -import java.util.HashMap; +import java.time.OffsetDateTime; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.*; /** * Listener for Desktop Agent events. @@ -55,7 +56,6 @@ public String getId() { } @Override - @SuppressWarnings("unchecked") public boolean filter(Map message) { String type = (String) message.get("type"); if (eventType == null) { @@ -79,17 +79,19 @@ public void action(Map message) { @Override public CompletionStage register() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "addEventListenerRequest"); + AddEventListenerRequest request = new AddEventListenerRequest(); + request.setType(AddEventListenerRequestType.ADD_EVENT_LISTENER_REQUEST); + request.setMeta(createMeta()); + + AddEventListenerRequestPayload payload = new AddEventListenerRequestPayload(); + payload.setType(toFDC3EventType(eventType)); + request.setPayload(payload); - Map payload = new HashMap<>(); - payload.put("type", eventType); - request.put("payload", payload); + Map requestMap = messaging.getConverter().toMap(request); messaging.register(this); - return messaging.>exchange(request, "addEventListenerResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "addEventListenerResponse", messageExchangeTimeout) .thenApply(response -> null); } @@ -97,5 +99,31 @@ public CompletionStage register() { public void unsubscribe() { messaging.unregister(id); } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } + + private FDC3EventType toFDC3EventType(String eventType) { + if (eventType == null) { + return null; + } + switch (eventType) { + case "userChannelChanged": + return FDC3EventType.USER_CHANNEL_CHANGED; + default: + return null; + } + } +} diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java index a6d24dfa..01651973 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java @@ -16,15 +16,16 @@ package org.finos.fdc3.proxy.listeners; -import java.util.HashMap; +import java.time.OffsetDateTime; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.*; /** * Event listener for private channel events. @@ -100,18 +101,20 @@ public void action(Map message) { @Override public CompletionStage register() { - Map request = new HashMap<>(); - request.put("meta", messaging.createMeta()); - request.put("type", "privateChannelAddEventListenerRequest"); + PrivateChannelAddEventListenerRequest request = new PrivateChannelAddEventListenerRequest(); + request.setType(PrivateChannelAddEventListenerRequestType.PRIVATE_CHANNEL_ADD_EVENT_LISTENER_REQUEST); + request.setMeta(createMeta()); - Map payload = new HashMap<>(); - payload.put("privateChannelId", channelId); - payload.put("listenerType", eventType); - request.put("payload", payload); + PrivateChannelAddEventListenerRequestPayload payload = new PrivateChannelAddEventListenerRequestPayload(); + payload.setPrivateChannelID(channelId); + payload.setListenerType(toPrivateChannelEventType(eventType)); + request.setPayload(payload); + + Map requestMap = messaging.getConverter().toMap(request); messaging.register(this); - return messaging.>exchange(request, "privateChannelAddEventListenerResponse", messageExchangeTimeout) + return messaging.>exchange(requestMap, "privateChannelAddEventListenerResponse", messageExchangeTimeout) .thenApply(response -> null); } @@ -130,5 +133,35 @@ public void registerSync() { throw new RuntimeException("Failed to register listener", e); } } -} + private AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(messaging.createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + AppIdentifier appId = messaging.getAppIdentifier(); + if (appId != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appId.getAppId()); + appId.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } + + 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: + return null; + } + } +} 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 index a20637e8..a090129b 100644 --- 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 @@ -29,6 +29,7 @@ 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.SchemaConverter; /** * Abstract base class for messaging implementations. @@ -39,9 +40,11 @@ public abstract class AbstractMessaging implements Messaging { private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final AppIdentifier appIdentifier; + private final SchemaConverter converter; protected AbstractMessaging(AppIdentifier appIdentifier) { this.appIdentifier = appIdentifier; + this.converter = new SchemaConverter(); } @Override @@ -160,6 +163,11 @@ public AppIdentifier getAppIdentifier() { return appIdentifier; } + @Override + public SchemaConverter getConverter() { + return converter; + } + @Override public abstract CompletionStage disconnect(); } diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index db9495e5..bfdd5707 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -41,6 +41,11 @@ jackson-databind 2.16.1 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.16.1 + 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..efc749ca --- /dev/null +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java @@ -0,0 +1,229 @@ +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.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.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + 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-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java index 71585476..e1d1fdee 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java @@ -98,4 +98,19 @@ public void setId(Map id) { } } + /** + * Create a Context from a Map. + * This is a convenience factory method for converting Map data to a Context object. + * + * @param map the map containing context data + * @return a new Context containing all entries from the map + */ + public static Context fromMap(Map map) { + if (map == null) { + return null; + } + Context context = new Context(); + context.putAll(map); + return context; + } } diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml index a16c8c8e..c08bb953 100644 --- a/fdc3-testing/pom.xml +++ b/fdc3-testing/pom.xml @@ -44,7 +44,7 @@ - org.finos.fdc3.api + org.finos.fdc3 fdc3-standard ${project.version} From 566af1711313267067c6d9fe3ed0d1944f519abd Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 12 Dec 2025 17:25:08 +0000 Subject: [PATCH 15/65] createMeta refactor --- .../java/org/finos/fdc3/proxy/Messaging.java | 5 ++-- .../fdc3/proxy/apps/DefaultAppSupport.java | 25 +++------------- .../fdc3/proxy/channels/DefaultChannel.java | 21 ++------------ .../proxy/channels/DefaultChannelSupport.java | 29 ++++--------------- .../proxy/channels/DefaultPrivateChannel.java | 19 +----------- .../heartbeat/DefaultHeartbeatSupport.java | 19 +----------- .../proxy/intents/DefaultIntentSupport.java | 24 +++------------ .../listeners/DefaultContextListener.java | 19 +----------- .../listeners/DefaultIntentListener.java | 18 +----------- .../listeners/DesktopAgentEventListener.java | 19 +----------- .../PrivateChannelEventListener.java | 19 +----------- .../proxy/messaging/AbstractMessaging.java | 16 +++++++++- 12 files changed, 40 insertions(+), 193 deletions(-) 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 index c8f1a937..dfa450f6 100644 --- 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 @@ -22,6 +22,7 @@ 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; /** @@ -61,9 +62,9 @@ public interface Messaging { /** * Create a metadata element to attach to outgoing messages. * - * @return the metadata map + * @return the metadata object */ - Map createMeta(); + AddContextListenerRequestMeta createMeta(); /** * Waits for a specific matching message. 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 index 9c699362..a184ea60 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.apps; -import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -58,7 +57,7 @@ public CompletionStage> findInstances(AppIdentifier app) { // Build typed request FindInstancesRequest request = new FindInstancesRequest(); request.setType(FindInstancesRequestType.FIND_INSTANCES_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); FindInstancesRequestPayload payload = new FindInstancesRequestPayload(); payload.setApp(toSchemaAppIdentifier(app)); @@ -88,7 +87,7 @@ public CompletionStage getAppMetadata(AppIdentifier app) { // Build typed request GetAppMetadataRequest request = new GetAppMetadataRequest(); request.setType(GetAppMetadataRequestType.GET_APP_METADATA_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); GetAppMetadataRequestPayload payload = new GetAppMetadataRequestPayload(); payload.setApp(toSchemaAppIdentifier(app)); @@ -116,7 +115,7 @@ public CompletionStage open(AppIdentifier app, Context context) { // Build typed request OpenRequest request = new OpenRequest(); request.setType(OpenRequestType.OPEN_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); OpenRequestPayload payload = new OpenRequestPayload(); payload.setApp(toSchemaAppIdentifier(app)); @@ -147,7 +146,7 @@ public CompletionStage getImplementationMetadata() { // Build typed request GetInfoRequest request = new GetInfoRequest(); request.setType(GetInfoRequestType.GET_INFO_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); request.setPayload(new GetInfoRequestPayload()); // Convert to Map for messaging @@ -170,22 +169,6 @@ public CompletionStage getImplementationMetadata() { // ============ Conversion helpers ============ - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - // Set source from messaging's app identifier - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } - private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); schemaApp.setAppID(app.getAppId()); 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 index a87861b5..d712ebcc 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.channels; -import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -24,7 +23,6 @@ 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.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; @@ -74,7 +72,7 @@ public Optional displayMetadata() { public CompletionStage broadcast(Context context) { BroadcastRequest request = new BroadcastRequest(); request.setType(BroadcastRequestType.BROADCAST_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); BroadcastRequestPayload payload = new BroadcastRequestPayload(); payload.setChannelID(id); @@ -96,7 +94,7 @@ public CompletionStage> getCurrentContext() { public CompletionStage> getCurrentContext(String contextType) { GetCurrentContextRequest request = new GetCurrentContextRequest(); request.setType(GetCurrentContextRequestType.GET_CURRENT_CONTEXT_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); GetCurrentContextRequestPayload payload = new GetCurrentContextRequestPayload(); payload.setChannelID(id); @@ -133,19 +131,4 @@ protected CompletionStage addContextListenerInner(String contextType, ); return listener.register().thenApply(v -> listener); } - - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } } 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 index 1f4a439b..b87af581 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.channels; -import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -29,7 +28,6 @@ import org.finos.fdc3.api.channel.PrivateChannel; import org.finos.fdc3.api.errors.ChannelError; 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.EventHandler; import org.finos.fdc3.api.types.Listener; @@ -112,7 +110,7 @@ public CompletionStage addEventListener(EventHandler handler, String t public CompletionStage getUserChannel() { GetCurrentChannelRequest request = new GetCurrentChannelRequest(); request.setType(GetCurrentChannelRequestType.GET_CURRENT_CHANNEL_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); request.setPayload(new GetCurrentChannelRequestPayload()); Map requestMap = messaging.getConverter().toMap(request); @@ -146,7 +144,7 @@ private CompletionStage> getUserChannelsCached() { public CompletionStage> getUserChannels() { GetUserChannelsRequest request = new GetUserChannelsRequest(); request.setType(GetUserChannelsRequestType.GET_USER_CHANNELS_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); request.setPayload(new GetUserChannelsRequestPayload()); Map requestMap = messaging.getConverter().toMap(request); @@ -178,7 +176,7 @@ public CompletionStage> getUserChannels() { public CompletionStage getOrCreate(String id) { GetOrCreateChannelRequest request = new GetOrCreateChannelRequest(); request.setType(GetOrCreateChannelRequestType.GET_OR_CREATE_CHANNEL_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); GetOrCreateChannelRequestPayload payload = new GetOrCreateChannelRequestPayload(); payload.setChannelID(id); @@ -207,7 +205,7 @@ public CompletionStage getOrCreate(String id) { public CompletionStage createPrivateChannel() { CreatePrivateChannelRequest request = new CreatePrivateChannelRequest(); request.setType(CreatePrivateChannelRequestType.CREATE_PRIVATE_CHANNEL_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); request.setPayload(new CreatePrivateChannelRequestPayload()); Map requestMap = messaging.getConverter().toMap(request); @@ -231,7 +229,7 @@ public CompletionStage createPrivateChannel() { public CompletionStage leaveUserChannel() { LeaveCurrentChannelRequest request = new LeaveCurrentChannelRequest(); request.setType(LeaveCurrentChannelRequestType.LEAVE_CURRENT_CHANNEL_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); request.setPayload(new LeaveCurrentChannelRequestPayload()); Map requestMap = messaging.getConverter().toMap(request); @@ -249,7 +247,7 @@ public CompletionStage leaveUserChannel() { public CompletionStage joinUserChannel(String id) { JoinUserChannelRequest request = new JoinUserChannelRequest(); request.setType(JoinUserChannelRequestType.JOIN_USER_CHANNEL_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); JoinUserChannelRequestPayload payload = new JoinUserChannelRequestPayload(); payload.setChannelID(id); @@ -286,21 +284,6 @@ Channel getCurrentChannelInternal() { // ============ Helper methods ============ - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } - private DisplayMetadata toApiDisplayMetadata(org.finos.fdc3.schema.DisplayMetadata schemaDisplayMetadata) { if (schemaDisplayMetadata == null) { return null; 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 index cf8d1e40..9e64366f 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.channels; -import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -24,7 +23,6 @@ import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.channel.PrivateChannel; -import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; @@ -72,7 +70,7 @@ messaging, messageExchangeTimeout, getId(), "disconnect", public void disconnect() { PrivateChannelDisconnectRequest request = new PrivateChannelDisconnectRequest(); request.setType(PrivateChannelDisconnectRequestType.PRIVATE_CHANNEL_DISCONNECT_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); PrivateChannelDisconnectRequestPayload payload = new PrivateChannelDisconnectRequestPayload(); payload.setChannelID(getId()); @@ -99,19 +97,4 @@ protected CompletionStage addContextListenerInner(String contextType, ); return listener.register().thenApply(v -> listener); } - - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } } 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 index 0089cb5a..5a28cca2 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.heartbeat; -import java.time.OffsetDateTime; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -24,7 +23,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -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; @@ -103,7 +101,7 @@ private void respondToHeartbeat(Map heartbeatEvent) { HeartbeatAcknowledgementRequest request = new HeartbeatAcknowledgementRequest(); request.setType(HeartbeatAcknowledgementRequestType.HEARTBEAT_ACKNOWLEDGEMENT_REQUEST); - AddContextListenerRequestMeta requestMeta = createMeta(); + AddContextListenerRequestMeta requestMeta = messaging.createMeta(); // Set the requestUuid to match the eventUuid for correlation if (eventUuid != null) { requestMeta.setRequestUUID(eventUuid); @@ -133,19 +131,4 @@ public CompletionStage disconnect() { scheduler.shutdown(); return CompletableFuture.completedFuture(null); } - - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } } 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 index 9d40f4dd..99b87d0e 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.intents; -import java.time.OffsetDateTime; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -65,7 +64,7 @@ public DefaultIntentSupport( public CompletionStage findIntent(String intent, Context context, String resultType) { FindIntentRequest request = new FindIntentRequest(); request.setType(FindIntentRequestType.FIND_INTENT_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); FindIntentRequestPayload payload = new FindIntentRequestPayload(); payload.setIntent(intent); @@ -102,7 +101,7 @@ public CompletionStage findIntent(String intent, Context context, Str public CompletionStage> findIntentsByContext(Context context) { FindIntentsByContextRequest request = new FindIntentsByContextRequest(); request.setType(FindIntentsByContextRequestType.FIND_INTENTS_BY_CONTEXT_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); FindIntentsByContextRequestPayload payload = new FindIntentsByContextRequestPayload(); payload.setContext(context); @@ -129,7 +128,7 @@ public CompletionStage> findIntentsByContext(Context context) { @Override public CompletionStage raiseIntent(String intent, Context context, AppIdentifier app) { - AddContextListenerRequestMeta meta = createMeta(); + AddContextListenerRequestMeta meta = messaging.createMeta(); String requestUuid = meta.getRequestUUID(); RaiseIntentRequest request = new RaiseIntentRequest(); @@ -185,7 +184,7 @@ public CompletionStage raiseIntent(String intent, Context cont @Override public CompletionStage raiseIntentForContext(Context context, AppIdentifier app) { - AddContextListenerRequestMeta meta = createMeta(); + AddContextListenerRequestMeta meta = messaging.createMeta(); String requestUuid = meta.getRequestUUID(); RaiseIntentForContextRequest request = new RaiseIntentForContextRequest(); @@ -249,21 +248,6 @@ public CompletionStage addIntentListener(String intent, IntentHandler // ============ Helper methods ============ - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } - private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); schemaApp.setAppID(app.getAppId()); 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 index 0151cf05..93f22c03 100644 --- 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 @@ -16,13 +16,11 @@ package org.finos.fdc3.proxy.listeners; -import java.time.OffsetDateTime; 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.api.types.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; @@ -111,7 +109,7 @@ public void action(Map message) { public CompletionStage register() { AddContextListenerRequest request = new AddContextListenerRequest(); request.setType(AddContextListenerRequestType.ADD_CONTEXT_LISTENER_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); AddContextListenerRequestPayload payload = new AddContextListenerRequestPayload(); payload.setChannelID(channelId); @@ -135,19 +133,4 @@ public CompletionStage unsubscribeAsync() { messaging.unregister(id); return CompletableFuture.completedFuture(null); } - - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } } 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 index a622879d..6afa9e0d 100644 --- 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 @@ -16,7 +16,6 @@ package org.finos.fdc3.proxy.listeners; -import java.time.OffsetDateTime; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -113,7 +112,7 @@ public java.util.Optional getInstanceId() { public CompletionStage register() { AddIntentListenerRequest request = new AddIntentListenerRequest(); request.setType(AddIntentListenerRequestType.ADD_INTENT_LISTENER_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); AddIntentListenerRequestPayload payload = new AddIntentListenerRequestPayload(); payload.setIntent(intent); @@ -131,19 +130,4 @@ public CompletionStage register() { public void unsubscribe() { messaging.unregister(id); } - - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } } 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 index 593a37c0..3faedb37 100644 --- 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 @@ -16,11 +16,9 @@ package org.finos.fdc3.proxy.listeners; -import java.time.OffsetDateTime; import java.util.Map; import java.util.concurrent.CompletionStage; -import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.api.types.Listener; @@ -81,7 +79,7 @@ public void action(Map message) { public CompletionStage register() { AddEventListenerRequest request = new AddEventListenerRequest(); request.setType(AddEventListenerRequestType.ADD_EVENT_LISTENER_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); AddEventListenerRequestPayload payload = new AddEventListenerRequestPayload(); payload.setType(toFDC3EventType(eventType)); @@ -100,21 +98,6 @@ public void unsubscribe() { messaging.unregister(id); } - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } - private FDC3EventType toFDC3EventType(String eventType) { if (eventType == null) { return null; diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java index 01651973..7e6ba748 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java @@ -16,11 +16,9 @@ package org.finos.fdc3.proxy.listeners; -import java.time.OffsetDateTime; import java.util.Map; import java.util.concurrent.CompletionStage; -import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.api.types.Listener; @@ -103,7 +101,7 @@ public void action(Map message) { public CompletionStage register() { PrivateChannelAddEventListenerRequest request = new PrivateChannelAddEventListenerRequest(); request.setType(PrivateChannelAddEventListenerRequestType.PRIVATE_CHANNEL_ADD_EVENT_LISTENER_REQUEST); - request.setMeta(createMeta()); + request.setMeta(messaging.createMeta()); PrivateChannelAddEventListenerRequestPayload payload = new PrivateChannelAddEventListenerRequestPayload(); payload.setPrivateChannelID(channelId); @@ -134,21 +132,6 @@ public void registerSync() { } } - private AddContextListenerRequestMeta createMeta() { - AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); - meta.setRequestUUID(messaging.createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - - AppIdentifier appId = messaging.getAppIdentifier(); - if (appId != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appId.getAppId()); - appId.getInstanceId().ifPresent(source::setInstanceID); - meta.setSource(source); - } - return meta; - } - private PrivateChannelEventType toPrivateChannelEventType(String eventType) { if (eventType == null) { return null; 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 index a090129b..4b945051 100644 --- 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 @@ -16,6 +16,7 @@ package org.finos.fdc3.proxy.messaging; +import java.time.OffsetDateTime; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -29,6 +30,7 @@ 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; /** @@ -60,7 +62,19 @@ protected AbstractMessaging(AppIdentifier appIdentifier) { public abstract void unregister(String id); @Override - public abstract Map createMeta(); + public AddContextListenerRequestMeta createMeta() { + AddContextListenerRequestMeta meta = new AddContextListenerRequestMeta(); + meta.setRequestUUID(createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + + if (appIdentifier != null) { + org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); + source.setAppID(appIdentifier.getAppId()); + appIdentifier.getInstanceId().ifPresent(source::setInstanceID); + meta.setSource(source); + } + return meta; + } @Override @SuppressWarnings("unchecked") From 33242fb818b134676293ed7c6111e0d4ac3bb34d Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 10:15:33 +0000 Subject: [PATCH 16/65] Tests compiling --- .../proxy/steps/ChannelSelectorSteps.java | 41 ++-- .../finos/fdc3/proxy/steps/ChannelSteps.java | 2 +- .../finos/fdc3/proxy/steps/GenericSteps.java | 37 ++- .../finos/fdc3/proxy/steps/IntentSteps.java | 2 +- .../finos/fdc3/proxy/support/ContextMap.java | 4 +- ...lector.java => SimpleChannelSelector.java} | 50 ++-- .../proxy/support}/SimpleIntentResolver.java | 82 +++---- .../fdc3/proxy/support/TestMessaging.java | 142 +++++++++--- .../responses/AddEventListenerResponse.java | 45 ++++ .../support/responses/AutomaticResponse.java | 26 +++ .../responses/ChannelStateResponse.java | 73 ++++++ .../CreatePrivateChannelResponse.java | 49 ++++ .../DisconnectPrivateChannelResponse.java | 39 ++++ .../responses/FindInstancesResponse.java | 49 ++++ .../FindIntentByContextResponse.java | 90 ++++++++ .../support/responses/FindIntentResponse.java | 117 ++++++++++ .../responses/GetAppMetadataResponse.java | 51 ++++ .../support/responses/GetInfoResponse.java | 58 +++++ .../responses/GetOrCreateChannelResponse.java | 68 ++++++ .../responses/GetUserChannelsResponse.java | 60 +++++ .../responses/IntentResultResponse.java | 60 +++++ .../proxy/support/responses/OpenResponse.java | 65 ++++++ .../RaiseIntentForContextResponse.java | 217 ++++++++++++++++++ .../responses/RaiseIntentResponse.java | 217 ++++++++++++++++++ .../responses/RegisterListenersResponse.java | 49 ++++ .../support/responses/ResponseSupport.java | 46 ++++ .../UnsubscribeListenersResponse.java | 45 ++++ .../org/finos/fdc3/testing/FDC3Testing.java | 106 --------- .../fdc3/testing/agent/ChannelSelector.java | 63 ----- .../fdc3/testing/agent/IntentResolver.java | 68 ------ .../testing/agent/SimpleChannelSelector.java | 78 ------- .../fdc3/testing/support/MatchingUtils.java | 3 +- 32 files changed, 1650 insertions(+), 452 deletions(-) rename fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/{TestChannelSelector.java => SimpleChannelSelector.java} (63%) rename {fdc3-testing/src/main/java/org/finos/fdc3/testing/agent => fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support}/SimpleIntentResolver.java (54%) create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AddEventListenerResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AutomaticResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/DisconnectPrivateChannelResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindInstancesResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentByContextResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetAppMetadataResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetInfoResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetOrCreateChannelResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetUserChannelsResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/IntentResultResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/OpenResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentForContextResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RegisterListenersResponse.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ResponseSupport.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/UnsubscribeListenersResponse.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java 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 index df89927b..1d94f056 100644 --- 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 @@ -16,15 +16,22 @@ 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.proxy.support.TestChannelSelector; +import org.finos.fdc3.proxy.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 org.finos.fdc3.testing.agent.SimpleIntentResolver; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; @@ -41,40 +48,48 @@ public ChannelSelectorSteps(CustomWorld world) { } @Given("A Channel Selector in {string} and a Desktop Agent in {string}") - public void aChannelSelectorAndDesktopAgent(String selectorField, String daField) { + 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<>())); } - TestChannelSelector ts = new TestChannelSelector(); + TestMessaging messaging = world.getMessaging(); + SimpleChannelSelector ts = new SimpleChannelSelector(world); world.set(selectorField, ts); - // TODO: Create DefaultChannelSupport, DefaultHeartbeatSupport, etc. - // For now, we just set up the basic structure - - // Store the selector and desktop agent references - world.set(daField, new Object()); // Placeholder - replace with actual DesktopAgentProxy + // 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); } @When("The first channel is selected via the channel selector in {string}") public void theFirstChannelIsSelected(String selectorField) { - TestChannelSelector selector = (TestChannelSelector) world.get(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) { - TestChannelSelector selector = (TestChannelSelector) world.get(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) { - TestChannelSelector selector = (TestChannelSelector) world.get(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 index a00fe67b..d13fe4a0 100644 --- 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 @@ -26,7 +26,7 @@ import org.finos.fdc3.proxy.support.ContextMap; import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.world.CustomWorld; -import org.finos.fdc3.testing.agent.SimpleChannelSelector; +import org.finos.fdc3.proxy.support.SimpleChannelSelector; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java index 739805fb..fcbaa374 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java @@ -16,15 +16,22 @@ 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.proxy.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 org.finos.fdc3.testing.agent.SimpleChannelSelector; -import org.finos.fdc3.testing.agent.SimpleIntentResolver; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; @@ -41,21 +48,28 @@ public GenericSteps(CustomWorld world) { } @Given("A Desktop Agent in {string}") - public void aDesktopAgentIn(String field) { + public void aDesktopAgentIn(String field) throws Exception { if (!world.hasMessaging()) { @SuppressWarnings("unchecked") Map> channelState = (Map>) world.get(ChannelSteps.CHANNEL_STATE); world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>())); } - // TODO: Create actual DesktopAgentProxy with: - // - DefaultChannelSupport - // - DefaultHeartbeatSupport - // - DefaultIntentSupport - // - DefaultAppSupport - - // For now, store a placeholder - world.set(field, new Object()); // Replace with actual DesktopAgentProxy + 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); + + DesktopAgentProxy da = new DesktopAgentProxy(hs, cs, is, as, connectables); + da.connect().toCompletableFuture().get(); + + world.set(field, da); world.set("result", null); } @@ -73,4 +87,3 @@ public void messagingReceivesHeartbeatEvent() { } } - 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 index ef5c0855..f27f2cd7 100644 --- 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 @@ -200,7 +200,7 @@ public void pipesIntentTo(String intentHandlerName, String field) { @Given("{string} returns a context item") public void returnsAContextItem(String intentHandlerName) { world.set(intentHandlerName, (Supplier) () -> { - Map id = new HashMap<>(); + Map id = new HashMap<>(); id.put("in", "one"); id.put("out", "two"); return new Context("fdc3.returned-intent", null, id); 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 index 8abaf665..daaa7d4d 100644 --- 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 @@ -30,13 +30,13 @@ public final class ContextMap { static { // fdc3.instrument - Map instrumentId = new HashMap<>(); + 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<>(); + Map countryId = new HashMap<>(); countryId.put("COUNTRY_ISOALPHA2", "SE"); countryId.put("COUNTRY_ISOALPHA3", "SWE"); Context country = new Context("fdc3.country", "Sweden", countryId); diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java similarity index 63% rename from fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java index 0cc47f60..2c54968e 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestChannelSelector.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleChannelSelector.java @@ -18,50 +18,54 @@ 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.testing.agent.ChannelSelector; +import org.finos.fdc3.proxy.channels.ChannelSelector; +import org.finos.fdc3.testing.world.PropsWorld; /** - * Test implementation of ChannelSelector for Cucumber tests. + * 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 TestChannelSelector implements ChannelSelector { +public class SimpleChannelSelector implements ChannelSelector { - private Consumer callback; + 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 CompletionStage updateChannel(String channelId, List availableChannels) { + public void updateChannel(String channelId, List availableChannels) { this.channelId = channelId; this.channels = new ArrayList<>(availableChannels); - return CompletableFuture.completedFuture(null); + world.set("channelId", channelId); + world.set("channels", availableChannels); } @Override public void setChannelChangeCallback(Consumer callback) { - this.callback = callback; - } - - @Override - public CompletionStage connect() { - System.out.println("TestChannelSelector was connected"); - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage disconnect() { - System.out.println("TestChannelSelector was disconnected"); - return CompletableFuture.completedFuture(null); + 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 (callback != null) { - callback.accept(channelId); + if (channelChangeCallback != null) { + channelChangeCallback.accept(channelId); } else { throw new IllegalStateException("Channel selected before Channel Change callback was set!"); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java similarity index 54% rename from fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java index b237e191..eb5b261e 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleIntentResolver.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/SimpleIntentResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.finos.fdc3.testing.agent; +package org.finos.fdc3.proxy.support; import java.util.ArrayList; import java.util.List; @@ -26,14 +26,16 @@ 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.IntentResult; +import org.finos.fdc3.api.types.AppIdentifier; +import org.finos.fdc3.proxy.intents.IntentResolutionChoice; +import org.finos.fdc3.proxy.intents.IntentResolver; import org.finos.fdc3.testing.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 cancels. + * unless the context type is "fdc3.cancel-me", in which case it returns null (cancelled). *

* This is equivalent to the TypeScript SimpleIntentResolver class. */ @@ -46,28 +48,10 @@ public SimpleIntentResolver(PropsWorld world) { } @Override - public CompletionStage connect() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage disconnect() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage intentChosen(IntentResult intentResult) { - world.set("intent-result", intentResult); - return CompletableFuture.completedFuture(intentResult); - } - - @Override - public CompletionStage> chooseIntent( - List appIntents, Context context) { - + 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(Optional.empty()); + return CompletableFuture.completedFuture(null); } // Select the first intent and first app @@ -76,43 +60,31 @@ public CompletionStage> chooseIntent( List apps = new ArrayList<>(firstIntent.getApps()); AppMetadata firstApp = apps.get(0); - // Store the resolution for testing + // Create an AppIdentifier from the AppMetadata + final String appId = firstApp.getAppId(); + final Optional instanceId = firstApp.getInstanceId(); + AppIdentifier appIdentifier = new AppIdentifier() { + @Override + public String getAppId() { + return appId; + } + + @Override + public Optional getInstanceId() { + return instanceId; + } + }; + + // Create the resolution choice IntentResolutionChoice resolution = new IntentResolutionChoice( - firstApp.getAppId(), - firstApp.getInstanceId().orElse(null), - intent.getName() + intent.getName(), + appIdentifier ); + // Store for testing verification world.set("intent-resolution", resolution); - return CompletableFuture.completedFuture(Optional.of(resolution)); - } - - /** - * Represents a choice made during intent resolution. - */ - public static class IntentResolutionChoice { - private final String appId; - private final String instanceId; - private final String intent; - - public IntentResolutionChoice(String appId, String instanceId, String intent) { - this.appId = appId; - this.instanceId = instanceId; - this.intent = intent; - } - - public String getAppId() { - return appId; - } - - public String getInstanceId() { - return instanceId; - } - - public String getIntent() { - return intent; - } + return CompletableFuture.completedFuture(resolution); } } 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 index 146641f7..c625d844 100644 --- 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 @@ -17,37 +17,76 @@ 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 java.util.function.Predicate; import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.context.Context; 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.*; /** * Test implementation of messaging for Cucumber tests. * Simulates the message exchange between the Desktop Agent and apps. */ -public class TestMessaging { +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; - private final String appId; - private final String instanceId; public TestMessaging(Map> channelState) { + super(new AppIdentifier() { + @Override + public String getAppId() { + return "cucumber-app"; + } + + @Override + public java.util.Optional getInstanceId() { + return java.util.Optional.of("cucumber-instance"); + } + }); this.channelState = channelState != null ? channelState : new HashMap<>(); - this.appId = "cucumber-app"; - this.instanceId = "cucumber-instance"; + + // 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)); + 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()); + this.automaticResponses.add(new AddEventListenerResponse()); } + @Override public String createUUID() { return UUID.randomUUID().toString(); } @@ -60,8 +99,43 @@ public List> getAllPosts() { return allPosts; } - public void post(Map message) { + @Override + public CompletionStage post(Map message) { 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) { @@ -72,34 +146,53 @@ public List getIntentDetails() { return intentDetails; } - public Map createMeta() { + /** + * 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", appId); - source.put("instanceId", instanceId); + source.put("appId", getAppIdentifier().getAppId()); + getAppIdentifier().getInstanceId().ifPresent(id -> source.put("instanceId", id)); meta.put("source", source); return meta; } - public Map createResponseMeta() { - Map meta = createMeta(); - meta.put("responseUuid", createUUID()); - return meta; - } - + /** + * Used in testing steps to create event metadata. + */ public Map createEventMeta() { - Map meta = createMeta(); + 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()); + getAppIdentifier().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + 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) { - // Process incoming messages - if (log != null) { - log.accept("Received message: " + message.get("type")); - } + 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() { @@ -122,14 +215,6 @@ public Map> getChannelState() { return channelState; } - public String getAppId() { - return appId; - } - - public String getInstanceId() { - return instanceId; - } - /** * Represents details about an intent that can be resolved. */ @@ -214,4 +299,3 @@ public void setTimeout(boolean 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..66f4d083 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AddEventListenerResponse.java @@ -0,0 +1,45 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..770faed2 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/AutomaticResponse.java @@ -0,0 +1,26 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +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..a6ec4781 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java @@ -0,0 +1,73 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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.api.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * Responds to getCurrentContext requests. + */ +public class ChannelStateResponse implements AutomaticResponse { + + private final Map> channelState; + + public ChannelStateResponse(Map> channelState) { + this.channelState = channelState; + } + + @Override + public boolean filter(String messageType) { + return "getCurrentContextRequest".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 contextType = (String) msgPayload.get("contextType"); + + List contexts = channelState.get(channelId); + 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 latest context + foundContext = contexts.get(contexts.size() - 1); + } + } + + Map payload = new HashMap<>(); + if (foundContext != null) { + payload.put("context", foundContext); + } + + Map response = new HashMap<>(); + response.put("type", "getCurrentContextResponse"); + 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/CreatePrivateChannelResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java new file mode 100644 index 00000000..1038ee28 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/CreatePrivateChannelResponse.java @@ -0,0 +1,49 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..f4e72f4b --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/DisconnectPrivateChannelResponse.java @@ -0,0 +1,39 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..53744f7e --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindInstancesResponse.java @@ -0,0 +1,49 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..add16224 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentByContextResponse.java @@ -0,0 +1,90 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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 HashSet<>(); + 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 HashMap<>(); + 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 HashMap<>(); + app.put("appId", detail.getApp().getAppId()); + detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + 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..cc57c00f --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/FindIntentResponse.java @@ -0,0 +1,117 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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()); + detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + 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..f0dc6fac --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetAppMetadataResponse.java @@ -0,0 +1,51 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..3a2732d5 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetInfoResponse.java @@ -0,0 +1,58 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..75883d2f --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetOrCreateChannelResponse.java @@ -0,0 +1,68 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..f701399e --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/GetUserChannelsResponse.java @@ -0,0 +1,60 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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.api.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..392c9916 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/IntentResultResponse.java @@ -0,0 +1,60 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +import java.util.HashMap; +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.context.Context; +import org.finos.fdc3.proxy.support.TestMessaging; +import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..b04a8d72 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/OpenResponse.java @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..5b869496 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentForContextResponse.java @@ -0,0 +1,217 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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 + if (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().isPresent() && + !targetInstanceId.equals(detail.getApp().getInstanceId().get())) { + 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()); + detail.getApp().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + + 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 HashSet<>(); + for (IntentDetail detail : relevant) { + if (detail.getIntent() != null) { + uniqueIntents.add(detail.getIntent()); + } + } + + List> appIntents = new ArrayList<>(); + for (String intentName : uniqueIntents) { + Map intentInfo = new HashMap<>(); + 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 HashMap<>(); + app.put("appId", detail.getApp().getAppId()); + detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + apps.add(app); + } + } + + Map appIntent = new HashMap<>(); + 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..5daac605 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RaiseIntentResponse.java @@ -0,0 +1,217 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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().isPresent() && + !targetInstanceId.equals(detail.getApp().getInstanceId().get())) { + 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()); + detail.getApp().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + + 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()); + detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + 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); + + 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..dce1dad9 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/RegisterListenersResponse.java @@ -0,0 +1,49 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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; + +import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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..2f95ca56 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ResponseSupport.java @@ -0,0 +1,46 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +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. + */ + @SuppressWarnings("unchecked") + 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..1cfefecb --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/UnsubscribeListenersResponse.java @@ -0,0 +1,45 @@ +/** + * Copyright 2023 Wellington Management Company LLP + */ +package org.finos.fdc3.proxy.support.responses; + +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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; + +/** + * 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-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java deleted file mode 100644 index e3c37b5a..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/FDC3Testing.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.testing; - -import org.finos.fdc3.testing.agent.ChannelSelector; -import org.finos.fdc3.testing.agent.IntentResolver; -import org.finos.fdc3.testing.agent.SimpleChannelSelector; -import org.finos.fdc3.testing.agent.SimpleIntentResolver; -import org.finos.fdc3.testing.steps.GenericSteps; -import org.finos.fdc3.testing.support.MatchingUtils; -import org.finos.fdc3.testing.world.PropsWorld; - -/** - * Main entry point for the FDC3 Testing framework. - *

- * This class provides convenient access to all testing components and constants. - *

- * Usage example: - *

- * // Create a test world
- * PropsWorld world = new PropsWorld();
- *
- * // Create step definitions (automatically registered with Cucumber)
- * GenericSteps steps = new GenericSteps(world);
- *
- * // Use matching utilities
- * Object value = MatchingUtils.handleResolve("{result.field}", world);
- *
- * // Create test agents
- * IntentResolver resolver = new SimpleIntentResolver(world);
- * ChannelSelector selector = new SimpleChannelSelector(world);
- * 
- */ -public final class FDC3Testing { - - /** - * Constant for channel state key in PropsWorld. - */ - public static final String CHANNEL_STATE = SimpleChannelSelector.CHANNEL_STATE; - - private FDC3Testing() { - // Utility class - prevent instantiation - } - - /** - * Create a new PropsWorld instance for test state. - * - * @return a new PropsWorld - */ - public static PropsWorld createWorld() { - return new PropsWorld(); - } - - /** - * Create a new SimpleIntentResolver for testing. - * - * @param world the PropsWorld to use - * @return a new SimpleIntentResolver - */ - public static IntentResolver createIntentResolver(PropsWorld world) { - return new SimpleIntentResolver(world); - } - - /** - * Create a new SimpleChannelSelector for testing. - * - * @param world the PropsWorld to use - * @return a new SimpleChannelSelector - */ - public static ChannelSelector createChannelSelector(PropsWorld world) { - return new SimpleChannelSelector(world); - } - - /** - * Get the MatchingUtils class for static utility access. - * - * @return the MatchingUtils class - */ - public static Class getMatchingUtils() { - return MatchingUtils.class; - } - - /** - * Get the GenericSteps class for step definition registration. - * - * @return the GenericSteps class - */ - public static Class getGenericSteps() { - return GenericSteps.class; - } -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java deleted file mode 100644 index bce2bd1f..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/ChannelSelector.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.testing.agent; - -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - -import org.finos.fdc3.api.channel.Channel; - -/** - * Interface for selecting channels. - *

- * Implementations of this interface handle the user interaction - * for selecting a channel from available options. - */ -public interface ChannelSelector { - - /** - * Update the current channel state. - * - * @param channelId the current channel ID, or null if not joined - * @param availableChannels the list of available channels - * @return a CompletionStage that completes when updated - */ - CompletionStage updateChannel(String channelId, List availableChannels); - - /** - * Set a callback to be invoked when the user changes channels. - * - * @param callback the callback, accepting the new channel ID (or null to leave) - */ - void setChannelChangeCallback(Consumer callback); - - /** - * Connect the selector (e.g., initialize UI components). - * - * @return a CompletionStage that completes when connected - */ - CompletionStage connect(); - - /** - * Disconnect the selector (e.g., cleanup UI components). - * - * @return a CompletionStage that completes when disconnected - */ - CompletionStage disconnect(); -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java deleted file mode 100644 index 6f5db0f6..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/IntentResolver.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.testing.agent; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -import org.finos.fdc3.api.context.Context; -import org.finos.fdc3.api.metadata.AppIntent; -import org.finos.fdc3.api.types.IntentResult; - -/** - * Interface for resolving intents to specific applications. - *

- * Implementations of this interface handle the user interaction - * for selecting an intent and target application when multiple - * options are available. - */ -public interface IntentResolver { - - /** - * Connect the resolver (e.g., initialize UI components). - * - * @return a CompletionStage that completes when connected - */ - CompletionStage connect(); - - /** - * Disconnect the resolver (e.g., cleanup UI components). - * - * @return a CompletionStage that completes when disconnected - */ - CompletionStage disconnect(); - - /** - * Called when an intent has been chosen and resolved. - * - * @param intentResult the result of the intent resolution - * @return a CompletionStage containing the intent result - */ - CompletionStage intentChosen(IntentResult intentResult); - - /** - * Choose an intent from a list of available intents. - * - * @param appIntents the list of available app intents - * @param context the context for the intent - * @return a CompletionStage containing the chosen resolution, or empty if cancelled - */ - CompletionStage> chooseIntent( - List appIntents, Context context); -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java deleted file mode 100644 index cda8aaa3..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/agent/SimpleChannelSelector.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.testing.agent; - -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.testing.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; - - public SimpleChannelSelector(PropsWorld world) { - this.world = world; - } - - @Override - public CompletionStage updateChannel(String channelId, List availableChannels) { - world.set("channelId", channelId); - world.set("channels", availableChannels); - return CompletableFuture.completedFuture(null); - } - - @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 simulateChannelChange(String channelId) { - if (channelChangeCallback != null) { - channelChangeCallback.accept(channelId); - } - } - - @Override - public CompletionStage connect() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage disconnect() { - return CompletableFuture.completedFuture(null); - } -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 7dd6f9a3..2edc9db4 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -76,7 +76,8 @@ public static Object handleResolve(String name, PropsWorld world) { } else { // Use JSONPath to resolve the value from props try { - Object propsAsJson = objectMapper.convertValue(world.getProps(), Object.class); + @SuppressWarnings("unchecked") + Map propsAsJson = objectMapper.convertValue(world.getProps(), Map.class); String jsonPath = "$." + stripped; return JsonPath.read(propsAsJson, jsonPath); } catch (PathNotFoundException e) { From e3b116196524f30c271529f152d79025791a0291 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 10:20:37 +0000 Subject: [PATCH 17/65] tidying up use of ObjectMapper --- fdc3-schema/pom.xml | 5 +++++ .../finos/fdc3/schema/SchemaConverter.java | 2 ++ fdc3-testing/pom.xml | 19 +++++++------------ .../fdc3/testing/support/MatchingUtils.java | 4 +++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index bfdd5707..184344cf 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -46,6 +46,11 @@ jackson-datatype-jsr310 2.16.1 + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.16.1 + 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 index efc749ca..e3a31b85 100644 --- a/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java @@ -5,6 +5,7 @@ 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; @@ -107,6 +108,7 @@ public SchemaConverter(ObjectMapper 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); return om; diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml index c08bb953..6d130d6c 100644 --- a/fdc3-testing/pom.xml +++ b/fdc3-testing/pom.xml @@ -49,6 +49,13 @@ ${project.version} + + + org.finos.fdc3 + fdc3-schema + ${project.version} + + io.cucumber @@ -80,18 +87,6 @@ ${jsonpath.version} - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - com.networknt diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 2edc9db4..bfbe3ece 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.finos.fdc3.schema.SchemaConverter; import org.finos.fdc3.testing.world.PropsWorld; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.PathNotFoundException; @@ -43,7 +44,8 @@ */ public final class MatchingUtils { - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final SchemaConverter converter = new SchemaConverter(); + private static final ObjectMapper objectMapper = converter.getObjectMapper(); private MatchingUtils() { // Utility class From 1f9e2ea10d2e81a204e209d526c1e3a7e9f11dcc Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 11:40:35 +0000 Subject: [PATCH 18/65] Using spring to run cucumber to fix PropsWorld creation issue --- .gitignore | 6 ++ .vscode/launch.json | 18 ++++++ fdc3-agent-proxy/pom.xml | 16 ++++- .../proxy/CucumberSpringConfiguration.java | 24 ++----- .../org/finos/fdc3/proxy/RunCucumberTest.java | 37 ----------- .../finos/fdc3/proxy/TestSpringConfig.java | 62 +++++++++++++++++++ .../{GenericSteps.java => AgentSteps.java} | 6 +- .../finos/fdc3/proxy/world/CustomWorld.java | 3 + fdc3-testing/pom.xml | 9 ++- .../fdc3/testing/steps/GenericSteps.java | 2 + .../fdc3/testing/support/MatchingUtils.java | 33 +++++----- 11 files changed, 134 insertions(+), 82 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/TestSpringConfig.java rename fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/{GenericSteps.java => AgentSteps.java} (96%) diff --git a/.gitignore b/.gitignore index a73cfdf2..209617e3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,9 @@ website/package-lock.json # Maven target target/ + +# Eclipse Ignore +.classpath +.project +.settings/* +*/.settings \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..3e2b3750 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "Attach to Maven Debug (port 5005)", + "request": "attach", + "hostName": "localhost", + "port": 5005 + } + ] +} \ No newline at end of file diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index c0407998..97a5dda3 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -61,10 +61,22 @@ io.cucumber - cucumber-picocontainer + cucumber-spring ${cucumber.version} test + + org.springframework + spring-context + 6.1.2 + test + + + org.springframework + spring-test + 6.1.2 + test + io.cucumber cucumber-junit-platform-engine @@ -118,7 +130,7 @@ cucumber.junit-platform.naming-strategy=long cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html - cucumber.glue=org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps + cucumber.glue=org.finos.fdc3.proxy,org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps cucumber.features=src/test/resources/features diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java index dddaf814..337ea82b 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java @@ -16,27 +16,13 @@ package org.finos.fdc3.proxy; -import org.finos.fdc3.proxy.world.CustomWorld; - -import io.cucumber.java.Before; -import io.cucumber.java.Scenario; +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.test.context.ContextConfiguration; /** - * Cucumber configuration and hooks. + * Cucumber Spring configuration entry point. */ +@CucumberContextConfiguration +@ContextConfiguration(classes = TestSpringConfig.class) public class CucumberSpringConfiguration { - - private final CustomWorld world; - - public CucumberSpringConfiguration(CustomWorld world) { - this.world = world; - } - - @Before - public void beforeScenario(Scenario scenario) { - // Reset world state before each scenario - world.getProps().clear(); - world.setMessaging(null); - } } - 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 deleted file mode 100644 index f51b861f..00000000 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2023 Wellington Management Company LLP - * - * 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.SelectClasspathResource; -import org.junit.platform.suite.api.Suite; - -import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; - -/** - * Cucumber test runner for FDC3 Agent Proxy tests. - */ -@Suite -@IncludeEngines("cucumber") -@SelectClasspathResource("features") -@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps") -@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty,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..6a03c299 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/TestSpringConfig.java @@ -0,0 +1,62 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 io.cucumber.spring.ScenarioScope; +import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.testing.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.Primary; + +/** + * 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", + "org.finos.fdc3.testing.steps" +}) +public class TestSpringConfig { + + /** + * Create CustomWorld as a scenario-scoped bean. + */ + @Bean + @ScenarioScope + 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 + public PropsWorld propsWorld(CustomWorld customWorld) { + return customWorld; + } +} + diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java similarity index 96% rename from fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java rename to fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java index fcbaa374..8304a8b7 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/GenericSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/AgentSteps.java @@ -37,13 +37,13 @@ import io.cucumber.java.en.When; /** - * Generic Cucumber step definitions for agent-proxy tests. + * Agent step definitions for agent-proxy tests. */ -public class GenericSteps { +public class AgentSteps { private final CustomWorld world; - public GenericSteps(CustomWorld world) { + public AgentSteps(CustomWorld world) { this.world = world; } 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 index 90879ced..8fea4791 100644 --- 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 @@ -22,6 +22,9 @@ /** * 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 { diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml index 6d130d6c..9eb7f27d 100644 --- a/fdc3-testing/pom.xml +++ b/fdc3-testing/pom.xml @@ -37,7 +37,6 @@ 7.15.0 5.10.1 2.16.1 - 2.9.0 1.3.1 @@ -80,11 +79,11 @@ ${junit.version} - + - com.jayway.jsonpath - json-path - ${jsonpath.version} + commons-jxpath + commons-jxpath + 1.3 diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 7be72aaf..88c6d21b 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -90,8 +90,10 @@ public void iCallWith(String field, String fnName) { @When("I call {string} with {string} with parameter {string}") public void iCallWithParameter(String field, String fnName, String param) { try { + System.out.println("Starting call"); Object object = handleResolve(field, world); Object paramValue = handleResolve(param, world); + System.out.println("Calling "+object+" with "+paramValue); Object result = invokeMethod(object, fnName, paramValue); world.set("result", result); } catch (Exception error) { diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index bfbe3ece..2804277b 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -26,8 +26,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.finos.fdc3.schema.SchemaConverter; import org.finos.fdc3.testing.world.PropsWorld; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.PathNotFoundException; +import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.JXPathNotFoundException; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; @@ -76,13 +76,12 @@ public static Object handleResolve(String name, PropsWorld world) { } else if (isNumeric(stripped)) { return Double.parseDouble(stripped); } else { - // Use JSONPath to resolve the value from props + // Use JXPath to resolve the value from props try { - @SuppressWarnings("unchecked") - Map propsAsJson = objectMapper.convertValue(world.getProps(), Map.class); - String jsonPath = "$." + stripped; - return JsonPath.read(propsAsJson, jsonPath); - } catch (PathNotFoundException e) { + JXPathContext context = JXPathContext.newContext(world); + context.setLenient(true); + return context.getValue(stripped); + } catch (JXPathNotFoundException e) { return null; } } @@ -127,8 +126,9 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob // Extract path before matches_type String path = field.substring(0, field.length() - "matches_type".length() - 1); try { - String dataJson = objectMapper.writeValueAsString(data); - valData = JsonPath.read(dataJson, "$." + path); + JXPathContext context = JXPathContext.newContext(data); + context.setLenient(true); + valData = context.getValue(path); } catch (Exception e) { world.log("Error extracting path: " + e.getMessage()); return false; @@ -161,10 +161,11 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob return false; } } else { - // Field value comparison using JSONPath + // Field value comparison using JXPath try { - String dataJson = objectMapper.writeValueAsString(data); - Object found = JsonPath.read(dataJson, "$." + field); + JXPathContext context = JXPathContext.newContext(data); + context.setLenient(true); + Object found = context.getValue(field); Object resolved = handleResolve(expected, world); if (!Objects.equals(asString(found), asString(resolved))) { @@ -172,11 +173,11 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob "Match failed on %s: '%s' vs '%s'", field, found, resolved)); return false; } - } catch (PathNotFoundException e) { + } catch (JXPathNotFoundException e) { world.log("Path not found: " + field); return false; - } catch (JsonProcessingException e) { - world.log("JSON processing error: " + e.getMessage()); + } catch (Exception e) { + world.log("Error: " + e.getMessage()); return false; } } From f29863f033ab037fb964d1f5efc4f4119129e892 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 12:05:11 +0000 Subject: [PATCH 19/65] Loading schemas --- .../fdc3/testing/steps/GenericSteps.java | 127 +++++++++++++++++- .../fdc3/testing/support/MatchingUtils.java | 36 +++-- 2 files changed, 138 insertions(+), 25 deletions(-) diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 88c6d21b..b9293303 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -16,7 +16,13 @@ package org.finos.fdc3.testing.steps; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -24,6 +30,11 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; import org.finos.fdc3.testing.support.MatchingUtils; import org.finos.fdc3.testing.world.PropsWorld; @@ -90,10 +101,8 @@ public void iCallWith(String field, String fnName) { @When("I call {string} with {string} with parameter {string}") public void iCallWithParameter(String field, String fnName, String param) { try { - System.out.println("Starting call"); Object object = handleResolve(field, world); Object paramValue = handleResolve(param, world); - System.out.println("Calling "+object+" with "+paramValue); Object result = invokeMethod(object, fnName, paramValue); world.set("result", result); } catch (Exception error) { @@ -265,9 +274,117 @@ public void weWaitForPeriod(String ms) throws InterruptedException { @Given("schemas loaded") public void schemasLoaded() { - // Schema loading would be configured externally - // The schemas map should be set up in the test configuration - world.log("Schemas should be loaded by test configuration"); + Map schemas = new HashMap<>(); + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + + // Find the fdc3-schema module's npm-work directory + // This works when running from the project root or any submodule + Path schemaDir = findSchemaDirectory(); + + if (schemaDir == null || !Files.exists(schemaDir)) { + world.log("Schema directory not found. Run 'mvn compile' on fdc3-schema first."); + return; + } + + // Load all API schemas + Path apiDir = schemaDir.resolve("api"); + if (Files.exists(apiDir)) { + try (Stream files = Files.list(apiDir)) { + files.filter(p -> p.toString().endsWith(".json")) + .forEach(file -> { + try { + String schemaContent = Files.readString(file); + JsonSchema schema = factory.getSchema(schemaContent); + // Use the schema $id or filename as the key + String schemaId = extractSchemaId(schemaContent, file.getFileName().toString()); + schemas.put(schemaId, schema); + world.log("Loaded schema: " + schemaId); + } catch (IOException e) { + world.log("Error loading schema " + file + ": " + e.getMessage()); + } + }); + } catch (IOException e) { + world.log("Error reading schema directory: " + e.getMessage()); + } + } + + // Load context schema + Path contextSchemaPath = findContextSchemaDirectory(); + if (contextSchemaPath != null) { + Path contextSchema = contextSchemaPath.resolve("context").resolve("context.schema.json"); + if (Files.exists(contextSchema)) { + try { + String schemaContent = Files.readString(contextSchema); + JsonSchema schema = factory.getSchema(schemaContent); + schemas.put("context", schema); + world.log("Loaded context schema"); + } catch (IOException e) { + world.log("Error loading context schema: " + e.getMessage()); + } + } + } + + world.set("schemas", schemas); + world.log("Loaded " + schemas.size() + " schemas"); + } + + /** + * Find the schema directory by checking various possible locations. + */ + private Path findSchemaDirectory() { + // Possible locations relative to current working directory + String[] possiblePaths = { + "fdc3-schema/target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas", + "../fdc3-schema/target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas", + "target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas" + }; + + for (String pathStr : possiblePaths) { + Path path = Paths.get(pathStr); + if (Files.exists(path)) { + return path; + } + } + return null; + } + + /** + * Find the context schema directory. + */ + private Path findContextSchemaDirectory() { + String[] possiblePaths = { + "fdc3-schema/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", + "../fdc3-schema/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", + "fdc3-context/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", + "../fdc3-context/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas" + }; + + for (String pathStr : possiblePaths) { + Path path = Paths.get(pathStr); + if (Files.exists(path)) { + return path; + } + } + return null; + } + + /** + * Extract the schema ID from the schema content or use the filename. + */ + private String extractSchemaId(String schemaContent, String filename) { + // Try to extract $id from the schema + // Simple regex approach - could use Jackson for more robust parsing + int idIndex = schemaContent.indexOf("\"$id\""); + if (idIndex >= 0) { + int colonIndex = schemaContent.indexOf(":", idIndex); + int quoteStart = schemaContent.indexOf("\"", colonIndex + 1); + int quoteEnd = schemaContent.indexOf("\"", quoteStart + 1); + if (quoteStart >= 0 && quoteEnd > quoteStart) { + return schemaContent.substring(quoteStart + 1, quoteEnd); + } + } + // Fall back to filename without extension + return filename.replace(".schema.json", "").replace(".json", ""); } // ========== Helper Methods ========== diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 2804277b..da9dbed3 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -31,7 +31,6 @@ import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; import io.cucumber.datatable.DataTable; @@ -51,6 +50,19 @@ private MatchingUtils() { // Utility class } + private static Object extractFromWorld(Object world, String expression) { + // Use JXPath to resolve the value from props + try { + JXPathContext context = JXPathContext.newContext(world); + context.setLenient(true); + String xpathName = "//" + expression.replaceAll("\\.", "/"); + return context.getValue(xpathName); + } catch (JXPathNotFoundException e) { + return null; + } + + } + /** * Resolve a field reference to its actual value. *

@@ -76,14 +88,7 @@ public static Object handleResolve(String name, PropsWorld world) { } else if (isNumeric(stripped)) { return Double.parseDouble(stripped); } else { - // Use JXPath to resolve the value from props - try { - JXPathContext context = JXPathContext.newContext(world); - context.setLenient(true); - return context.getValue(stripped); - } catch (JXPathNotFoundException e) { - return null; - } + return extractFromWorld(world, stripped); } } else { return name; @@ -125,14 +130,7 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob if (field.length() > "matches_type".length()) { // Extract path before matches_type String path = field.substring(0, field.length() - "matches_type".length() - 1); - try { - JXPathContext context = JXPathContext.newContext(data); - context.setLenient(true); - valData = context.getValue(path); - } catch (Exception e) { - world.log("Error extracting path: " + e.getMessage()); - return false; - } + valData = extractFromWorld(world, path); } // Validate against schema @@ -163,9 +161,7 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob } else { // Field value comparison using JXPath try { - JXPathContext context = JXPathContext.newContext(data); - context.setLenient(true); - Object found = context.getValue(field); + Object found = extractFromWorld(data, field); Object resolved = handleResolve(expected, world); if (!Objects.equals(asString(found), asString(resolved))) { From 50e10f3bd11cfe8d54179d14f086dd7dc6a46869 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 12:17:41 +0000 Subject: [PATCH 20/65] broadcast test working --- .../fdc3/proxy/messaging/AbstractMessaging.java | 1 + .../finos/fdc3/testing/support/MatchingUtils.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) 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 index 4b945051..02a60df6 100644 --- 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 @@ -70,6 +70,7 @@ public AddContextListenerRequestMeta createMeta() { if (appIdentifier != null) { org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); source.setAppID(appIdentifier.getAppId()); + source.setDesktopAgent("testing-da"); appIdentifier.getInstanceId().ifPresent(source::setInstanceID); meta.setSource(source); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index da9dbed3..426d9a1f 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -109,6 +109,15 @@ private static boolean isNumeric(String str) { return false; } } + + private static JsonSchema findSchema(Map schemas, String name) { + for (Map.Entry entry : schemas.entrySet()) { + if (entry.getKey().endsWith(name + ".schema.json")) { + return entry.getValue(); + } + } + return null; + } /** * Check if a table row matches the given data object. @@ -141,7 +150,8 @@ public static boolean doesRowMatch(PropsWorld world, Map row, Ob return false; } - JsonSchema schema = schemas.get(expected); + JsonSchema schema = findSchema(schemas,expected); + if (schema == null) { world.log("No schema found for " + expected); return false; From 8b07df98a7d3c7cec55b0f63ef927c75e6804cff Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 14:22:33 +0000 Subject: [PATCH 21/65] Fixed event / context handlers in tests --- .../finos/fdc3/proxy/steps/ChannelSteps.java | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) 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 index d13fe4a0..3a053194 100644 --- 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 @@ -23,6 +23,10 @@ import java.util.function.Consumer; 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.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.proxy.support.ContextMap; import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.world.CustomWorld; @@ -157,25 +161,50 @@ public void isAPrivateChannelOnDisconnectEvent(String field, String channel) { public void pipesTypesTo(String typeHandlerName, String field) { List types = new ArrayList<>(); world.set(field, types); - world.set(typeHandlerName, (Consumer) s -> types.add(s)); + + ContextHandler ch = new ContextHandler() { + + @Override + public void handleContext(Context context, ContextMetadata metadata) { + types.add(context.getType()); + + } + }; + + world.set(typeHandlerName, ch); } @Given("{string} pipes events to {string}") public void pipesEventsTo(String typeHandlerName, String field) { - List events = new ArrayList<>(); + List> events = new ArrayList<>(); world.set(field, events); - world.set(typeHandlerName, (Consumer>) event -> { - if (event != null) { - events.add(event.get("details")); - } - }); + + EventHandler eh = new EventHandler() { + + @Override + public void handleEvent(FDC3Event event) { + events.add(event); + } + }; + + 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); - world.set(contextHandlerName, (Consumer) contexts::add); + + ContextHandler ch = new ContextHandler() { + + @Override + public void handleContext(Context context, ContextMetadata metadata) { + contexts.add(context); + + } + }; + + world.set(contextHandlerName, ch); } @When("messaging receives {string}") From 12e83dc14b2748655c78b2c4df005dcd7d3b6197 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 14:49:25 +0000 Subject: [PATCH 22/65] Added AbstractListener --- TODO.md | 4 + .../proxy/channels/DefaultChannelSupport.java | 2 +- .../DefaultUserChannelContextListener.java | 106 +++++++++++ .../channels/UserChannelContextListener.java | 81 ++------ .../proxy/listeners/AbstractListener.java | 177 ++++++++++++++++++ .../listeners/DefaultContextListener.java | 83 ++++---- .../listeners/DefaultIntentListener.java | 55 ++---- .../listeners/DesktopAgentEventListener.java | 59 +++--- .../PrivateChannelEventListener.java | 61 +++--- 9 files changed, 401 insertions(+), 227 deletions(-) create mode 100644 TODO.md create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractListener.java diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..d83bf020 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +## TODO + +- Remove all the wellington headers +- Documentation in the main FDC3 repo. 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 index b87af581..d33156a1 100644 --- 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 @@ -273,7 +273,7 @@ public CompletionStage joinUserChannel(String id) { @Override public CompletionStage addContextListener(ContextHandler handler, String type) { - UserChannelContextListener listener = new UserChannelContextListener(this, messaging, messageExchangeTimeout, type, handler); + DefaultUserChannelContextListener listener = new DefaultUserChannelContextListener(this, messaging, messageExchangeTimeout, type, 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..df43bceb --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java @@ -0,0 +1,106 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 -> notifyChannelChange()); + } + + /** + * Called when the user channel changes. + */ + @Override + public void changeChannel(Channel channel) { + super.changeChannel(channel); + } + + /** + * Notify the handler of current context after channel change. + */ + private CompletionStage notifyChannelChange() { + Channel currentChannel = channelSupport.getCurrentChannelInternal(); + if (currentChannel != null) { + return currentChannel.getCurrentContext(contextType) + .thenAccept(contextOpt -> { + contextOpt.ifPresent(context -> handler.handleContext(context, null)); + }); + } + return CompletableFuture.completedFuture(null); + } + + @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 index 054a791b..42b479cd 100644 --- 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 @@ -16,80 +16,23 @@ 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.types.ContextHandler; -import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.proxy.listeners.DefaultContextListener; +import org.finos.fdc3.api.types.Listener; +import org.finos.fdc3.proxy.listeners.RegisterableListener; /** - * Context listener that tracks user channel changes. + * 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 class UserChannelContextListener extends DefaultContextListener { - - private final DefaultChannelSupport channelSupport; - - public UserChannelContextListener( - 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()); - } +public interface UserChannelContextListener extends Listener, RegisterableListener { /** - * Called when the user channel changes. + * 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. + * + * @param channel the new channel, or null if leaving channel */ - public CompletionStage changeChannel() { - Channel currentChannel = channelSupport.getCurrentChannelInternal(); - if (currentChannel != null) { - return currentChannel.getCurrentContext(contextType) - .thenAccept(contextOpt -> { - contextOpt.ifPresent(context -> handler.handleContext(context, null)); - }); - } - return java.util.concurrent.CompletableFuture.completedFuture(null); - } - - @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); - } + void changeChannel(Channel channel); } - 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..346ae8ce --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractListener.java @@ -0,0 +1,177 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 void unsubscribe() { + if (this.id != null) { + messaging.unregister(this.id); + + // Send unsubscribe request to the server + 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); + + // Fire and forget - we don't wait for the response + messaging.>exchange(request, unsubscribeResponseType, messageExchangeTimeout); + } else { + throw new RuntimeException("This listener doesn't have an id and hence can't be removed!"); + } + } + + /** + * Unsubscribe asynchronously and wait for server acknowledgement. + * + * @return a CompletionStage that completes when unsubscribed + */ + public CompletionStage unsubscribeAsync() { + 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/DefaultContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/DefaultContextListener.java index 93f22c03..79ad8c5a 100644 --- 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 @@ -16,28 +16,24 @@ package org.finos.fdc3.proxy.listeners; +import java.util.HashMap; 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.context.Context; import org.finos.fdc3.api.types.ContextHandler; -import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.schema.*; +import org.finos.fdc3.proxy.channels.UserChannelContextListener; /** * Default implementation of a context listener. + * Extends AbstractListener to handle registration/unregistration. */ -public class DefaultContextListener implements RegisterableListener, Listener { +public class DefaultContextListener extends AbstractListener implements UserChannelContextListener { - protected final Messaging messaging; - protected final long messageExchangeTimeout; - protected final String channelId; + protected String channelId; protected final String contextType; - protected final ContextHandler handler; protected final String messageType; - private final String id; public DefaultContextListener( Messaging messaging, @@ -55,18 +51,44 @@ public DefaultContextListener( String contextType, ContextHandler handler, String messageType) { - this.messaging = messaging; - this.messageExchangeTimeout = messageExchangeTimeout; + super( + messaging, + messageExchangeTimeout, + handler, + "addContextListenerRequest", + "addContextListenerResponse", + "contextListenerUnsubscribeRequest", + "contextListenerUnsubscribeResponse" + ); this.channelId = channelId; this.contextType = contextType; - this.handler = handler; this.messageType = messageType; - this.id = messaging.createUUID(); } @Override - public String getId() { - return id; + public void changeChannel(Channel channel) { + if (channel == null) { + this.channelId = null; + } else { + this.channelId = channel.getId(); + // Get current context from the channel + channel.getCurrentContext(contextType) + .thenAccept(context -> { + if (context.isPresent()) { + handler.handleContext(context.get(), null); + } + }); + } + } + + @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 @@ -104,33 +126,4 @@ public void action(Map message) { Context context = Context.fromMap(contextMap); handler.handleContext(context, null); } - - @Override - public CompletionStage register() { - AddContextListenerRequest request = new AddContextListenerRequest(); - request.setType(AddContextListenerRequestType.ADD_CONTEXT_LISTENER_REQUEST); - request.setMeta(messaging.createMeta()); - - AddContextListenerRequestPayload payload = new AddContextListenerRequestPayload(); - payload.setChannelID(channelId); - payload.setContextType(contextType); - request.setPayload(payload); - - Map requestMap = messaging.getConverter().toMap(request); - - messaging.register(this); - - return messaging.>exchange(requestMap, "addContextListenerResponse", messageExchangeTimeout) - .thenApply(response -> null); - } - - @Override - public void unsubscribe() { - messaging.unregister(id); - } - - public CompletionStage unsubscribeAsync() { - messaging.unregister(id); - return CompletableFuture.completedFuture(null); - } } 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 index 6afa9e0d..db31af60 100644 --- 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 @@ -16,43 +16,47 @@ package org.finos.fdc3.proxy.listeners; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletionStage; 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.api.types.IntentHandler; -import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.schema.*; /** * Default implementation of an intent listener. + * Extends AbstractListener to handle registration/unregistration. */ -public class DefaultIntentListener implements RegisterableListener, Listener { +public class DefaultIntentListener extends AbstractListener { - private final Messaging messaging; private final String intent; - private final IntentHandler handler; - private final long messageExchangeTimeout; - private final String id; public DefaultIntentListener( Messaging messaging, String intent, IntentHandler handler, long messageExchangeTimeout) { - this.messaging = messaging; + super( + messaging, + messageExchangeTimeout, + handler, + "addIntentListenerRequest", + "addIntentListenerResponse", + "intentListenerUnsubscribeRequest", + "intentListenerUnsubscribeResponse" + ); this.intent = intent; - this.handler = handler; - this.messageExchangeTimeout = messageExchangeTimeout; - this.id = messaging.createUUID(); } @Override - public String getId() { - return id; + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + payload.put("intent", intent); + request.put("payload", payload); + return request; } @Override @@ -107,27 +111,4 @@ public java.util.Optional getInstanceId() { handler.handleIntent(context, contextMetadata); } - - @Override - public CompletionStage register() { - AddIntentListenerRequest request = new AddIntentListenerRequest(); - request.setType(AddIntentListenerRequestType.ADD_INTENT_LISTENER_REQUEST); - request.setMeta(messaging.createMeta()); - - AddIntentListenerRequestPayload payload = new AddIntentListenerRequestPayload(); - payload.setIntent(intent); - request.setPayload(payload); - - Map requestMap = messaging.getConverter().toMap(request); - - messaging.register(this); - - return messaging.>exchange(requestMap, "addIntentListenerResponse", messageExchangeTimeout) - .thenApply(response -> null); - } - - @Override - public void unsubscribe() { - messaging.unregister(id); - } } 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 index 3faedb37..d22a90e3 100644 --- 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 @@ -16,41 +16,49 @@ package org.finos.fdc3.proxy.listeners; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletionStage; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; -import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.schema.*; +import org.finos.fdc3.schema.FDC3EventType; /** * Listener for Desktop Agent events. + * Extends AbstractListener to handle registration/unregistration. */ -public class DesktopAgentEventListener implements RegisterableListener, Listener { +public class DesktopAgentEventListener extends AbstractListener { - private final Messaging messaging; - private final long messageExchangeTimeout; private final String eventType; - private final EventHandler handler; - private final String id; public DesktopAgentEventListener( Messaging messaging, long messageExchangeTimeout, String eventType, EventHandler handler) { - this.messaging = messaging; - this.messageExchangeTimeout = messageExchangeTimeout; + super( + messaging, + messageExchangeTimeout, + handler, + "addEventListenerRequest", + "addEventListenerResponse", + "eventListenerUnsubscribeRequest", + "eventListenerUnsubscribeResponse" + ); this.eventType = eventType; - this.handler = handler; - this.id = messaging.createUUID(); } @Override - public String getId() { - return id; + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + FDC3EventType fdc3EventType = toFDC3EventType(eventType); + if (fdc3EventType != null) { + payload.put("type", fdc3EventType.toValue()); + } + request.put("payload", payload); + return request; } @Override @@ -75,29 +83,6 @@ public void action(Map message) { handler.handleEvent(event); } - @Override - public CompletionStage register() { - AddEventListenerRequest request = new AddEventListenerRequest(); - request.setType(AddEventListenerRequestType.ADD_EVENT_LISTENER_REQUEST); - request.setMeta(messaging.createMeta()); - - AddEventListenerRequestPayload payload = new AddEventListenerRequestPayload(); - payload.setType(toFDC3EventType(eventType)); - request.setPayload(payload); - - Map requestMap = messaging.getConverter().toMap(request); - - messaging.register(this); - - return messaging.>exchange(requestMap, "addEventListenerResponse", messageExchangeTimeout) - .thenApply(response -> null); - } - - @Override - public void unsubscribe() { - messaging.unregister(id); - } - private FDC3EventType toFDC3EventType(String eventType) { if (eventType == null) { return null; diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java index 7e6ba748..89906e27 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java @@ -16,26 +16,22 @@ package org.finos.fdc3.proxy.listeners; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletionStage; import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.FDC3Event; -import org.finos.fdc3.api.types.Listener; import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.schema.*; +import org.finos.fdc3.schema.PrivateChannelEventType; /** * Event listener for private channel events. + * Extends AbstractListener to handle registration/unregistration. */ -public class PrivateChannelEventListener implements RegisterableListener, Listener { +public class PrivateChannelEventListener extends AbstractListener { - private final Messaging messaging; - private final long messageExchangeTimeout; private final String channelId; private final String eventType; - private final EventHandler handler; - private final String id; public PrivateChannelEventListener( Messaging messaging, @@ -43,17 +39,30 @@ public PrivateChannelEventListener( String channelId, String eventType, EventHandler handler) { - this.messaging = messaging; - this.messageExchangeTimeout = messageExchangeTimeout; + super( + messaging, + messageExchangeTimeout, + handler, + "privateChannelAddEventListenerRequest", + "privateChannelAddEventListenerResponse", + "privateChannelUnsubscribeEventListenerRequest", + "privateChannelUnsubscribeEventListenerResponse" + ); this.channelId = channelId; this.eventType = eventType; - this.handler = handler; - this.id = messaging.createUUID(); } @Override - public String getId() { - return id; + protected Map buildSubscribeRequest() { + Map request = new HashMap<>(); + Map payload = new HashMap<>(); + payload.put("privateChannelId", channelId); + PrivateChannelEventType pcEventType = toPrivateChannelEventType(eventType); + if (pcEventType != null) { + payload.put("listenerType", pcEventType.toValue()); + } + request.put("payload", payload); + return request; } @Override @@ -97,30 +106,6 @@ public void action(Map message) { handler.handleEvent(event); } - @Override - public CompletionStage register() { - PrivateChannelAddEventListenerRequest request = new PrivateChannelAddEventListenerRequest(); - request.setType(PrivateChannelAddEventListenerRequestType.PRIVATE_CHANNEL_ADD_EVENT_LISTENER_REQUEST); - request.setMeta(messaging.createMeta()); - - PrivateChannelAddEventListenerRequestPayload payload = new PrivateChannelAddEventListenerRequestPayload(); - payload.setPrivateChannelID(channelId); - payload.setListenerType(toPrivateChannelEventType(eventType)); - request.setPayload(payload); - - Map requestMap = messaging.getConverter().toMap(request); - - messaging.register(this); - - return messaging.>exchange(requestMap, "privateChannelAddEventListenerResponse", messageExchangeTimeout) - .thenApply(response -> null); - } - - @Override - public void unsubscribe() { - messaging.unregister(id); - } - /** * Register synchronously. */ From 8df68089c832927491351db9a1daf95a8442eeca Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 16:45:07 +0000 Subject: [PATCH 23/65] Using correct feature files --- fdc3-agent-proxy/pom.xml | 34 +- .../fdc3/proxy/channels/DefaultChannel.java | 6 + .../resources/features/app-channels.feature | 116 ------ .../resources/features/app-metadata.feature | 35 -- .../test/resources/features/broadcast.feature | 32 -- .../resources/features/find-intents.feature | 74 ---- .../test/resources/features/heartbeat.feature | 18 - .../features/intent-listener.feature | 42 --- .../resources/features/intent-results.feature | 128 ------- .../src/test/resources/features/open.feature | 52 --- .../private-channels-deprecated.feature | 70 ---- .../features/private-channels.feature | 139 ------- .../resources/features/raise-intents.feature | 73 ---- .../features/user-channel-selector.feature | 18 - .../features/user-channel-sync.feature | 45 --- .../resources/features/user-channels.feature | 341 ------------------ .../src/test/resources/features/utils.feature | 10 - .../org/finos/fdc3/api/channel/Channel.java | 4 + .../fdc3/testing/steps/GenericSteps.java | 21 +- 19 files changed, 54 insertions(+), 1204 deletions(-) delete mode 100644 fdc3-agent-proxy/src/test/resources/features/app-channels.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/app-metadata.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/broadcast.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/find-intents.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/heartbeat.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/intent-listener.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/intent-results.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/open.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/private-channels.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/raise-intents.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/user-channels.feature delete mode 100644 fdc3-agent-proxy/src/test/resources/features/utils.feature diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index 97a5dda3..cb04bfbf 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -20,6 +20,8 @@ 11 7.15.0 5.10.1 + + ${project.basedir}/../../FDC3/packages/fdc3-agent-proxy/test/features @@ -109,9 +111,39 @@ 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 @@ -131,7 +163,7 @@ cucumber.junit-platform.naming-strategy=long cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html cucumber.glue=org.finos.fdc3.proxy,org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps - cucumber.features=src/test/resources/features + cucumber.features=classpath:features 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 index d712ebcc..deecf881 100644 --- 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 @@ -121,6 +121,12 @@ public CompletionStage addContextListener(String contextType, ContextH return addContextListenerInner(contextType, handler); } + @Override + @Deprecated + public CompletionStage addContextListener(ContextHandler handler) { + return addContextListener(null, handler); + } + protected CompletionStage addContextListenerInner(String contextType, ContextHandler handler) { DefaultContextListener listener = new DefaultContextListener( messaging, diff --git a/fdc3-agent-proxy/src/test/resources/features/app-channels.feature b/fdc3-agent-proxy/src/test/resources/features/app-channels.feature deleted file mode 100644 index a3339d7b..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/app-channels.feature +++ /dev/null @@ -1,116 +0,0 @@ -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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameter "{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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "{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" with parameter "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" with parameters "{true}" and "{resultHandler}" - Then "{result}" is an error - And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{true}" - Then "{result}" is an error - - Scenario: Destructured channel methods - broadcast and addContextListener - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I destructure methods "addContextListener", "broadcast" from "{channel1}" - And I call destructured "addContextListener" with parameters "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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I destructure methods "broadcast", "getCurrentContext" from "{channel1}" - And I call destructured "broadcast" with parameter "{instrumentContext}" - And I call destructured "getCurrentContext" with parameter "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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I destructure methods "addContextListener", "broadcast" from "{channel1}" - And I call destructured "addContextListener" with parameters "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 | \ No newline at end of file diff --git a/fdc3-agent-proxy/src/test/resources/features/app-metadata.feature b/fdc3-agent-proxy/src/test/resources/features/app-metadata.feature deleted file mode 100644 index 1a5cdbef..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/app-metadata.feature +++ /dev/null @@ -1,35 +0,0 @@ -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" with parameter "{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" with parameter "{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/features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/features/broadcast.feature deleted file mode 100644 index b0d9ac54..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/broadcast.feature +++ /dev/null @@ -1,32 +0,0 @@ -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" with parameter "channel-name" - And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" with parameter "{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" with parameter "{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" with parameter "one" - And I call "{api}" with "broadcast" with parameter "{instrumentContext}" - Then messaging will have posts - | payload.channelId | payload.context.type | payload.context.name | matches_type | - | one | {null} | {null} | joinUserChannelRequest | - | {null} | {null} | {null} | getUserChannelsRequest | - | {null} | {null} | {null} | getCurrentChannelRequest | - | one | fdc3.instrument | Apple | broadcastRequest | diff --git a/fdc3-agent-proxy/src/test/resources/features/find-intents.feature b/fdc3-agent-proxy/src/test/resources/features/find-intents.feature deleted file mode 100644 index d7fc546b..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/find-intents.feature +++ /dev/null @@ -1,74 +0,0 @@ -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" with parameter "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" with parameter "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" with parameters "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" with parameters "OrderFood" and "{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" with parameter "{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" with parameter "{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/features/heartbeat.feature b/fdc3-agent-proxy/src/test/resources/features/heartbeat.feature deleted file mode 100644 index 0123dd29..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/heartbeat.feature +++ /dev/null @@ -1,18 +0,0 @@ -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/features/intent-listener.feature b/fdc3-agent-proxy/src/test/resources/features/intent-listener.feature deleted file mode 100644 index bcaa3bba..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/intent-listener.feature +++ /dev/null @@ -1,42 +0,0 @@ -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" with parameters "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 | some-app-id | - 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" with parameters "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" with parameters "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" with parameters "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/features/intent-results.feature b/fdc3-agent-proxy/src/test/resources/features/intent-results.feature deleted file mode 100644 index 83eae1db..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/intent-results.feature +++ /dev/null @@ -1,128 +0,0 @@ -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" with parameters "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" with parameters "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" with parameters "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" with parameters "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: Context Data is returned in the result - Given Raise Intent returns a context of "{instrumentContext}" - When I call "{api}" with "raiseIntent" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/open.feature b/fdc3-agent-proxy/src/test/resources/features/open.feature deleted file mode 100644 index dc588092..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/open.feature +++ /dev/null @@ -1,52 +0,0 @@ -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" with parameters "{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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature b/fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature deleted file mode 100644 index d03f459d..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/private-channels-deprecated.feature +++ /dev/null @@ -1,70 +0,0 @@ -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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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/features/private-channels.feature b/fdc3-agent-proxy/src/test/resources/features/private-channels.feature deleted file mode 100644 index e498f21b..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/private-channels.feature +++ /dev/null @@ -1,139 +0,0 @@ -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" with parameters "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" with parameters "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: 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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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" with parameters "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" with parameter "{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" with parameters "fdc3.instrument" and "{resultHandler}" - And I call destructured "broadcast" with parameter "{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/features/raise-intents.feature b/fdc3-agent-proxy/src/test/resources/features/raise-intents.feature deleted file mode 100644 index 952af5b0..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/raise-intents.feature +++ /dev/null @@ -1,73 +0,0 @@ -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" with parameters "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" with parameters "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" with parameters "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" with parameter "{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" with parameters "{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" with parameter "{cancelContext}" - Then "{result}" is an error with message "UserCancelledResolution" - And messaging will have posts - | payload.context.type | matches_type | - | fdc3.cancel-me | raiseIntentForContextRequest | diff --git a/fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature b/fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature deleted file mode 100644 index cecbb46f..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/user-channel-selector.feature +++ /dev/null @@ -1,18 +0,0 @@ -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/features/user-channel-sync.feature b/fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature deleted file mode 100644 index 298a7696..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/user-channel-sync.feature +++ /dev/null @@ -1,45 +0,0 @@ -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" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "one" - And we wait for a period of "600" 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" with parameter "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 | - | {null} | {null} | {null} | getCurrentChannelRequest | - | 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" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "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/features/user-channels.feature b/fdc3-agent-proxy/src/test/resources/features/user-channels.feature deleted file mode 100644 index fdfadcd6..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/user-channels.feature +++ /dev/null @@ -1,341 +0,0 @@ -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" with parameter "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 | - | {null} | getCurrentChannelRequest | - - Scenario: Changing Channel via Deprecated API - You should be able to join a channel knowing it's ID. - - When I call "{api}" with "joinChannel" with parameter "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 | - | {null} | getCurrentChannelRequest | - - Scenario: Adding a Typed Listener on a given User Channel - Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "{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" with parameter "one" - And I call "{api}" with "addContextListener" with parameter "{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" with parameters "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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "{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" with parameters "{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" with parameter "one" - And I call "{api}" with "getCurrentChannel" - And I refer to "{result}" as "theChannel" - And I call "{api}" with "broadcast" with parameter "{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 | - | {null} | {null} | {null} | getCurrentChannelRequest | - | {null} | {null} | {null} | getCurrentChannelRequest | - | 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" with parameter "one" - And I call "{api}" with "getCurrentChannel" - And I refer to "{result}" as "theChannel" - And messaging receives "{instrumentMessageOne}" - And I call "{theChannel}" with "getCurrentContext" with parameter "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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "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" with parameters "{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" with parameters "unknownEventType" and "{typesHandler}" - Then "{result}" is an error with message "UnknownEventType" - - 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 | - And messaging will have posts - | meta.source.appId | meta.source.instanceId | matches_type | - | cucumber-app | cucumber-instance | getUserChannelsRequest | - - Scenario: Destructured joinUserChannel and getCurrentChannel work correctly - When I destructure method "joinUserChannel" from "{api}" - And I call destructured "joinUserChannel" with parameter "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 | - | {null} | getCurrentChannelRequest | - - Scenario: Destructured channel getCurrentContext after broadcast - Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "{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" with parameter "one" - And I call destructured "broadcast" with parameter "{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" with parameter "one" - And I call destructured "addContextListener" with parameters "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" with parameters "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 | diff --git a/fdc3-agent-proxy/src/test/resources/features/utils.feature b/fdc3-agent-proxy/src/test/resources/features/utils.feature deleted file mode 100644 index 6298470e..00000000 --- a/fdc3-agent-proxy/src/test/resources/features/utils.feature +++ /dev/null @@ -1,10 +0,0 @@ -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-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java index 2a44f748..614c72e2 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java @@ -84,4 +84,8 @@ enum Type { * Optional metadata about each context message received, including the app that originated the message, SHOULD be provided by the desktop agent implementation. */ CompletionStage addContextListener(String contextType, ContextHandler handler); + + @Deprecated + CompletionStage addContextListener(ContextHandler handler); + } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index b9293303..d4ed8a4c 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -16,7 +16,15 @@ package org.finos.fdc3.testing.steps; -import java.io.File; +import static org.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; +import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static org.finos.fdc3.testing.support.MatchingUtils.matchData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.lang.reflect.Method; import java.nio.file.Files; @@ -32,23 +40,17 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.finos.fdc3.testing.world.PropsWorld; + import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; -import org.finos.fdc3.testing.support.MatchingUtils; -import org.finos.fdc3.testing.world.PropsWorld; - import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import static org.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; -import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static org.finos.fdc3.testing.support.MatchingUtils.matchData; -import static org.junit.jupiter.api.Assertions.*; - /** * Generic Cucumber step definitions for FDC3 testing. * This is equivalent to the TypeScript generic.steps.ts module. @@ -213,7 +215,6 @@ public void isUndefined(String field) { @Then("{string} is empty") public void isEmpty(String field) { - @SuppressWarnings("unchecked") List data = (List) handleResolve(field, world); assertTrue(data.isEmpty()); } From 243fc23ca4f90f79a7bbb2ddc46b2f8b65fdddd8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 15 Dec 2025 16:56:57 +0000 Subject: [PATCH 24/65] Better logging --- .../org/finos/fdc3/proxy/CucumberHooks.java | 42 ++++++++++++++ .../fdc3/testing/steps/GenericSteps.java | 1 - .../finos/fdc3/testing/world/PropsWorld.java | 56 ++++++++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java new file mode 100644 index 00000000..6ac7aece --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java @@ -0,0 +1,42 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 io.cucumber.java.Before; +import io.cucumber.java.Scenario; +import org.finos.fdc3.proxy.world.CustomWorld; + +/** + * Cucumber hooks for setting up and tearing down test state. + */ +public class CucumberHooks { + + private final CustomWorld world; + + public CucumberHooks(CustomWorld world) { + this.world = world; + } + + /** + * Before each scenario, set the scenario on the world for logging. + */ + @Before + public void beforeScenario(Scenario scenario) { + world.setScenario(scenario); + } +} + diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index d4ed8a4c..ebf4155d 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -299,7 +299,6 @@ public void schemasLoaded() { // Use the schema $id or filename as the key String schemaId = extractSchemaId(schemaContent, file.getFileName().toString()); schemas.put(schemaId, schema); - world.log("Loaded schema: " + schemaId); } catch (IOException e) { world.log("Error loading schema " + file + ": " + e.getMessage()); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java index 6e3f38c2..ed7bea6f 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.Map; +import io.cucumber.java.Scenario; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +32,7 @@ public class PropsWorld { private static final Logger logger = LoggerFactory.getLogger(PropsWorld.class); private final Map props = new HashMap<>(); + private Scenario scenario; /** * Get the props map containing all test state. @@ -72,12 +74,62 @@ public boolean has(String key) { } /** - * Log a message (equivalent to cw.log in TypeScript). + * Set the Cucumber scenario for logging. + * This should be called from a @Before hook. + * + * @param scenario the current Cucumber scenario + */ + public void setScenario(Scenario scenario) { + this.scenario = scenario; + } + + /** + * Get the current scenario. + * + * @return the current Cucumber scenario + */ + public Scenario getScenario() { + return scenario; + } + + /** + * Log a message to the Cucumber report. + * This is equivalent to this.log() in TypeScript Cucumber World. * * @param message the message to log */ public void log(String message) { + // Log to SLF4J for console output logger.info(message); + + // Log to Cucumber report if scenario is available + if (scenario != null) { + scenario.log(message); + } } -} + /** + * Attach content to the Cucumber report. + * + * @param data the data to attach + * @param mediaType the MIME type of the data + * @param name optional name for the attachment + */ + public void attach(byte[] data, String mediaType, String name) { + if (scenario != null) { + scenario.attach(data, mediaType, name); + } + } + + /** + * Attach text content to the Cucumber report. + * + * @param data the text to attach + * @param mediaType the MIME type (e.g., "text/plain", "application/json") + */ + public void attach(String data, String mediaType) { + if (scenario != null) { + scenario.attach(data, mediaType, null); + } + } +} From faeaea9033e3d8ed24d80deb9b4d2b40abcc49ce Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Tue, 16 Dec 2025 13:16:07 +0000 Subject: [PATCH 25/65] Fixing various null handling issues which caused schema mismatches --- .../fdc3/proxy/apps/DefaultAppSupport.java | 23 ++ .../proxy/intents/DefaultIntentSupport.java | 12 + .../listeners/DefaultIntentListener.java | 6 + .../finos/fdc3/proxy/steps/IntentSteps.java | 5 + .../proxy/support/SimpleIntentResolver.java | 6 + .../fdc3/proxy/support/TestMessaging.java | 5 + fdc3-schema/scripts/generate-mixins.js | 260 ++++++++++++++++++ .../finos/fdc3/schema/NullHandlingMixin.java | 207 ++++++++++++++ .../finos/fdc3/schema/SchemaConverter.java | 4 + .../finos/fdc3/api/types/AppIdentifier.java | 5 + .../fdc3/testing/support/MatchingUtils.java | 8 +- 11 files changed, 539 insertions(+), 2 deletions(-) create mode 100644 fdc3-schema/scripts/generate-mixins.js create mode 100644 fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java 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 index a184ea60..31918c54 100644 --- 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 @@ -179,6 +179,7 @@ private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { String appId = schemaApp.getAppID(); String instanceId = schemaApp.getInstanceID(); + String desktopAgent = schemaApp.getDesktopAgent(); return new AppIdentifier() { @Override public String getAppId() { @@ -189,12 +190,18 @@ public String getAppId() { public Optional getInstanceId() { return Optional.ofNullable(instanceId); } + + @Override + public Optional getDesktopAgent() { + return Optional.ofNullable(desktopAgent); + } }; } private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppMetadata schemaApp) { String appId = schemaApp.getAppID(); String instanceId = schemaApp.getInstanceID(); + String desktopAgent = schemaApp.getDesktopAgent(); return new AppIdentifier() { @Override public String getAppId() { @@ -205,12 +212,18 @@ public String getAppId() { public Optional getInstanceId() { return Optional.ofNullable(instanceId); } + + @Override + public Optional getDesktopAgent() { + return Optional.ofNullable(desktopAgent); + } }; } private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaMetadata) { String appId = schemaMetadata.getAppID(); String instanceId = schemaMetadata.getInstanceID(); + String desktopAgent = schemaMetadata.getDesktopAgent(); String name = schemaMetadata.getName(); String title = schemaMetadata.getTitle(); String description = schemaMetadata.getDescription(); @@ -229,6 +242,11 @@ public Optional getInstanceId() { return Optional.ofNullable(instanceId); } + @Override + public Optional getDesktopAgent() { + return Optional.ofNullable(desktopAgent); + } + @Override public Optional getName() { return Optional.ofNullable(name); @@ -361,6 +379,11 @@ public Optional getInstanceId() { return Optional.of("unknown"); } + @Override + public Optional getDesktopAgent() { + return Optional.empty(); + } + @Override public Optional getName() { return Optional.empty(); 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 index 99b87d0e..8a650c4c 100644 --- 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 @@ -258,6 +258,7 @@ private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { String appId = schemaApp.getAppID(); String instanceId = schemaApp.getInstanceID(); + String desktopAgent = schemaApp.getDesktopAgent(); return new AppIdentifier() { @Override public String getAppId() { @@ -268,6 +269,11 @@ public String getAppId() { public Optional getInstanceId() { return Optional.ofNullable(instanceId); } + + @Override + public Optional getDesktopAgent() { + return Optional.ofNullable(desktopAgent); + } }; } @@ -327,6 +333,7 @@ public List getApps() { private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaApp) { String appId = schemaApp.getAppID(); String instanceId = schemaApp.getInstanceID(); + String desktopAgent = schemaApp.getDesktopAgent(); String name = schemaApp.getName(); String title = schemaApp.getTitle(); String description = schemaApp.getDescription(); @@ -342,6 +349,11 @@ public Optional getInstanceId() { return Optional.ofNullable(instanceId); } + @Override + public Optional getDesktopAgent() { + return Optional.ofNullable(desktopAgent); + } + @Override public Optional getName() { return Optional.ofNullable(name); 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 index db31af60..5ebd1433 100644 --- 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 @@ -91,6 +91,7 @@ public void action(Map message) { if (sourceMap != null) { String sourceAppId = (String) sourceMap.get("appId"); String sourceInstanceId = (String) sourceMap.get("instanceId"); + String sourceDesktopAgent = (String) sourceMap.get("desktopAgent"); contextMetadata = new ContextMetadata() { @Override public AppIdentifier getSource() { @@ -104,6 +105,11 @@ public String getAppId() { public java.util.Optional getInstanceId() { return java.util.Optional.ofNullable(sourceInstanceId); } + + @Override + public java.util.Optional getDesktopAgent() { + return java.util.Optional.ofNullable(sourceDesktopAgent); + } }; } }; 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 index f27f2cd7..af20438b 100644 --- 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 @@ -233,6 +233,11 @@ public String getAppId() { return appId; } + @Override + public Optional getDesktopAgent() { + return Optional.of("some-desktop-agent"); + } + @Override public Optional getInstanceId() { return Optional.ofNullable(instanceId); 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 index eb5b261e..606c15ed 100644 --- 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 @@ -63,6 +63,7 @@ public CompletionStage chooseIntent(List appI // Create an AppIdentifier from the AppMetadata final String appId = firstApp.getAppId(); final Optional instanceId = firstApp.getInstanceId(); + final Optional desktopAgent = firstApp.getDesktopAgent(); AppIdentifier appIdentifier = new AppIdentifier() { @Override public String getAppId() { @@ -73,6 +74,11 @@ public String getAppId() { public Optional getInstanceId() { return instanceId; } + + @Override + public Optional getDesktopAgent() { + return desktopAgent; + } }; // Create the resolution choice 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 index c625d844..4789fb59 100644 --- 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 @@ -62,6 +62,11 @@ public String getAppId() { public java.util.Optional getInstanceId() { return java.util.Optional.of("cucumber-instance"); } + + @Override + public java.util.Optional getDesktopAgent() { + return java.util.Optional.of("testing-da"); + } }); this.channelState = channelState != null ? channelState : new HashMap<>(); diff --git a/fdc3-schema/scripts/generate-mixins.js b/fdc3-schema/scripts/generate-mixins.js new file mode 100644 index 00000000..4ec1b5c5 --- /dev/null +++ b/fdc3-schema/scripts/generate-mixins.js @@ -0,0 +1,260 @@ +#!/usr/bin/env node +/** + * Script to generate NullHandlingMixin.java for FDC3 schema types. + * + * This script reads JSON schema files and identifies fields that are: + * - NOT required (optional) + * - Do NOT allow null as a type + * + * For these fields, we need @JsonInclude(Include.NON_NULL) to omit them + * when null, otherwise JSON Schema validation fails. + * + * Usage: node generate-mixins.js [schema-dir] [output-dir] + */ + +const fs = require('fs'); +const path = require('path'); + +// Configuration +const SCHEMA_DIR = process.argv[2] || path.join(__dirname, '../target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas/api'); +const OUTPUT_DIR = process.argv[3] || path.join(__dirname, '../src/main/java/org/finos/fdc3/schema'); +const PACKAGE_NAME = 'org.finos.fdc3.schema'; + +// Track all mixins we need to generate +const mixinsNeeded = new Map(); // className -> Set of field names + +/** + * Check if a schema type allows null + */ +function allowsNull(schema) { + if (!schema) return false; + + // Check for explicit null type + if (schema.type === 'null') return true; + + // Check for array of types including null + if (Array.isArray(schema.type) && schema.type.includes('null')) return true; + + // Check for oneOf/anyOf with null + if (schema.oneOf) { + return schema.oneOf.some(s => s.type === 'null'); + } + if (schema.anyOf) { + return schema.anyOf.some(s => s.type === 'null'); + } + + return false; +} + +/** + * Convert schema name to Java class name + */ +function toJavaClassName(name) { + return name.charAt(0).toUpperCase() + name.slice(1); +} + +/** + * Convert property name to Java getter method name + */ +function toGetterName(propName) { + return 'get' + propName.charAt(0).toUpperCase() + propName.slice(1); +} + +/** + * Get the Java type for a schema property (simplified) + */ +function getJavaType(schema, propName) { + if (!schema) return 'Object'; + + // Handle $ref - just use Object for simplicity + if (schema.$ref) return 'Object'; + + const type = schema.type; + if (type === 'string') return 'String'; + if (type === 'integer') return 'Long'; + if (type === 'number') return 'Double'; + if (type === 'boolean') return 'Boolean'; + if (type === 'array') return 'Object'; + if (type === 'object') return 'Object'; + + return 'Object'; +} + +/** + * Process a schema definition and find optional non-nullable fields + */ +function processDefinition(name, schema, schemaFile) { + if (!schema || schema.type !== 'object' || !schema.properties) { + return; + } + + const required = new Set(schema.required || []); + const optionalNonNullFields = []; + + for (const [propName, propSchema] of Object.entries(schema.properties)) { + // Skip if required + if (required.has(propName)) continue; + + // Skip if allows null + if (allowsNull(propSchema)) continue; + + // Skip if it's just "true" (any type allowed) + if (propSchema === true) continue; + + // This field needs NON_NULL + optionalNonNullFields.push({ + name: propName, + type: getJavaType(propSchema, propName), + jsonProperty: propName + }); + } + + if (optionalNonNullFields.length > 0) { + const className = toJavaClassName(name); + if (!mixinsNeeded.has(className)) { + mixinsNeeded.set(className, []); + } + mixinsNeeded.get(className).push(...optionalNonNullFields); + } +} + +/** + * Process a schema file + */ +function processSchemaFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const schema = JSON.parse(content); + + // Process $defs + if (schema.$defs) { + for (const [name, def] of Object.entries(schema.$defs)) { + processDefinition(name, def, filePath); + } + } + + // Process definitions (older style) + if (schema.definitions) { + for (const [name, def] of Object.entries(schema.definitions)) { + processDefinition(name, def, filePath); + } + } + } catch (e) { + console.error(`Error processing ${filePath}: ${e.message}`); + } +} + +/** + * Generate a single inner mixin class + */ +function generateInnerMixin(className, fields) { + // Deduplicate fields by name + const uniqueFields = new Map(); + for (const field of fields) { + uniqueFields.set(field.name, field); + } + + const fieldDefs = Array.from(uniqueFields.values()).map(field => { + return ` @JsonProperty("${field.jsonProperty}") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract ${field.type} ${toGetterName(field.name)}();`; + }).join('\n\n'); + + return ` public static abstract class ${className}Mixin { +${fieldDefs} + }`; +} + +/** + * Generate the complete NullHandlingMixin.java file + */ +function generateNullHandlingMixin() { + const sortedClassNames = Array.from(mixinsNeeded.keys()).sort(); + + // Generate registration calls + const registrations = sortedClassNames + .map(className => ` om.addMixIn(${className}.class, ${className}Mixin.class);`) + .join('\n'); + + // Generate inner mixin classes + const innerClasses = sortedClassNames + .map(className => generateInnerMixin(className, mixinsNeeded.get(className))) + .join('\n\n'); + + return `package ${PACKAGE_NAME}; + +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 FILE - Do not edit manually! + * Regenerate with: node scripts/generate-mixins.js + * + * 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. + */ +public final class NullHandlingMixin { + + private NullHandlingMixin() { + // Utility class + } + + /** + * Register all mix-ins with the given ObjectMapper. + */ + public static void registerAll(ObjectMapper om) { +${registrations} + } + + // === Mix-in classes for each schema type === + +${innerClasses} +} +`; +} + +// Main execution +console.log('Scanning schema files in:', SCHEMA_DIR); + +// Check if schema directory exists +if (!fs.existsSync(SCHEMA_DIR)) { + console.error(`Schema directory not found: ${SCHEMA_DIR}`); + console.error('Run "mvn generate-sources" first to download schemas.'); + process.exit(1); +} + +// Read all schema files +const schemaFiles = fs.readdirSync(SCHEMA_DIR) + .filter(f => f.endsWith('.schema.json')) + .map(f => path.join(SCHEMA_DIR, f)); + +// Process api.schema.json which has shared definitions +const apiSchemaPath = path.join(SCHEMA_DIR, 'api.schema.json'); +if (fs.existsSync(apiSchemaPath)) { + processSchemaFile(apiSchemaPath); +} + +// Process each schema file +for (const file of schemaFiles) { + processSchemaFile(file); +} + +console.log(`\nFound ${mixinsNeeded.size} classes needing mixins:`); +for (const [className, fields] of mixinsNeeded) { + // Deduplicate + const uniqueFields = new Set(fields.map(f => f.name)); + console.log(` - ${className} (${uniqueFields.size} fields)`); +} + +// Generate the single NullHandlingMixin.java file +const outputPath = path.join(OUTPUT_DIR, 'NullHandlingMixin.java'); +const content = generateNullHandlingMixin(); + +fs.writeFileSync(outputPath, content); +console.log(`\nGenerated: ${outputPath}`); + +console.log('\nDone!'); 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..c4d1f240 --- /dev/null +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java @@ -0,0 +1,207 @@ +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. + */ +public final class NullHandlingMixin { + + private NullHandlingMixin() { + // Utility class + } + + /** + * Register all mix-ins with the given ObjectMapper. + */ + public static void registerAll(ObjectMapper om) { + om.addMixIn(AppIdentifier.class, AppIdentifierMixin.class); + om.addMixIn(AppMetadata.class, AppMetadataMixin.class); + om.addMixIn(ImplementationMetadata.class, ImplementationMetadataMixin.class); + om.addMixIn(BroadcastEventPayload.class, BroadcastEventPayloadMixin.class); + om.addMixIn(Channel.class, ChannelMixin.class); + om.addMixIn(DisplayMetadata.class, DisplayMetadataMixin.class); + om.addMixIn(FindIntentRequestPayload.class, FindIntentRequestPayloadMixin.class); + om.addMixIn(FindIntentsByContextRequestPayload.class, FindIntentsByContextRequestPayloadMixin.class); + om.addMixIn(Icon.class, IconMixin.class); + om.addMixIn(Image.class, ImageMixin.class); + om.addMixIn(IntentEventPayload.class, IntentEventPayloadMixin.class); + om.addMixIn(IntentMetadata.class, IntentMetadataMixin.class); + om.addMixIn(OpenRequestPayload.class, OpenRequestPayloadMixin.class); + om.addMixIn(RaiseIntentForContextRequestPayload.class, RaiseIntentForContextRequestPayloadMixin.class); + om.addMixIn(RaiseIntentRequestPayload.class, RaiseIntentRequestPayloadMixin.class); + } + + // === Mix-in classes for each schema type === + + public static abstract class AppIdentifierMixin { + @JsonProperty("instanceId") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getInstanceId(); + + @JsonProperty("desktopAgent") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getDesktopAgent(); + } + + public static abstract class AppMetadataMixin { + @JsonProperty("name") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getName(); + + @JsonProperty("version") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getVersion(); + + @JsonProperty("title") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getTitle(); + + @JsonProperty("tooltip") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getTooltip(); + + @JsonProperty("description") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getDescription(); + + @JsonProperty("icons") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getIcons(); + + @JsonProperty("screenshots") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getScreenshots(); + + @JsonProperty("resultType") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getResultType(); + + @JsonProperty("instanceId") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getInstanceId(); + + @JsonProperty("instanceMetadata") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getInstanceMetadata(); + + @JsonProperty("desktopAgent") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getDesktopAgent(); + } + + public static abstract class ImplementationMetadataMixin { + @JsonProperty("optionalFeatures") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getOptionalFeatures(); + + @JsonProperty("providerVersion") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getProviderVersion(); + } + + 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 DisplayMetadataMixin { + @JsonProperty("name") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getName(); + + @JsonProperty("color") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getColor(); + + @JsonProperty("glyph") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getGlyph(); + } + + 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 IconMixin { + @JsonProperty("size") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getSize(); + + @JsonProperty("type") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getType(); + } + + public static abstract class ImageMixin { + @JsonProperty("size") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getSize(); + + @JsonProperty("type") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getType(); + + @JsonProperty("label") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getLabel(); + } + + public static abstract class IntentEventPayloadMixin { + @JsonProperty("originatingApp") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract Object getOriginatingApp(); + } + + public static abstract class IntentMetadataMixin { + @JsonProperty("displayName") + @JsonInclude(JsonInclude.Include.NON_NULL) + abstract String getDisplayName(); + } + + 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 index e3a31b85..84c6a716 100644 --- a/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/SchemaConverter.java @@ -111,6 +111,10 @@ private static ObjectMapper createObjectMapper() { 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; } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java index fddffb24..7e06abae 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java @@ -31,4 +31,9 @@ public interface AppIdentifier { /** An optional instance identifier, indicating that this object represents a specific instance of the application described.*/ public Optional getInstanceId(); + + /** + * Identifier of the desktop agent, used in bridging. + */ + public Optional getDesktopAgent(); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 426d9a1f..0e97e4de 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -56,11 +56,15 @@ private static Object extractFromWorld(Object world, String expression) { JXPathContext context = JXPathContext.newContext(world); context.setLenient(true); String xpathName = "//" + expression.replaceAll("\\.", "/"); - return context.getValue(xpathName); + Object result = context.getValue(xpathName); + // Unwrap Optional if needed + if (result instanceof java.util.Optional) { + return ((java.util.Optional) result).orElse(null); + } + return result; } catch (JXPathNotFoundException e) { return null; } - } /** From cc05d85dbb087e7d28fab702d9e1bf16baa0ec5c Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Tue, 16 Dec 2025 13:16:29 +0000 Subject: [PATCH 26/65] mixin generator --- fdc3-schema/scripts/generate-mixins.js | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/fdc3-schema/scripts/generate-mixins.js b/fdc3-schema/scripts/generate-mixins.js index 4ec1b5c5..50e42c30 100644 --- a/fdc3-schema/scripts/generate-mixins.js +++ b/fdc3-schema/scripts/generate-mixins.js @@ -28,13 +28,13 @@ const mixinsNeeded = new Map(); // className -> Set of field names */ function allowsNull(schema) { if (!schema) return false; - + // Check for explicit null type if (schema.type === 'null') return true; - + // Check for array of types including null if (Array.isArray(schema.type) && schema.type.includes('null')) return true; - + // Check for oneOf/anyOf with null if (schema.oneOf) { return schema.oneOf.some(s => s.type === 'null'); @@ -42,7 +42,7 @@ function allowsNull(schema) { if (schema.anyOf) { return schema.anyOf.some(s => s.type === 'null'); } - + return false; } @@ -65,10 +65,10 @@ function toGetterName(propName) { */ function getJavaType(schema, propName) { if (!schema) return 'Object'; - + // Handle $ref - just use Object for simplicity if (schema.$ref) return 'Object'; - + const type = schema.type; if (type === 'string') return 'String'; if (type === 'integer') return 'Long'; @@ -76,7 +76,7 @@ function getJavaType(schema, propName) { if (type === 'boolean') return 'Boolean'; if (type === 'array') return 'Object'; if (type === 'object') return 'Object'; - + return 'Object'; } @@ -87,20 +87,20 @@ function processDefinition(name, schema, schemaFile) { if (!schema || schema.type !== 'object' || !schema.properties) { return; } - + const required = new Set(schema.required || []); const optionalNonNullFields = []; - + for (const [propName, propSchema] of Object.entries(schema.properties)) { // Skip if required if (required.has(propName)) continue; - + // Skip if allows null if (allowsNull(propSchema)) continue; - + // Skip if it's just "true" (any type allowed) if (propSchema === true) continue; - + // This field needs NON_NULL optionalNonNullFields.push({ name: propName, @@ -108,7 +108,7 @@ function processDefinition(name, schema, schemaFile) { jsonProperty: propName }); } - + if (optionalNonNullFields.length > 0) { const className = toJavaClassName(name); if (!mixinsNeeded.has(className)) { @@ -125,14 +125,14 @@ function processSchemaFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); const schema = JSON.parse(content); - + // Process $defs if (schema.$defs) { for (const [name, def] of Object.entries(schema.$defs)) { processDefinition(name, def, filePath); } } - + // Process definitions (older style) if (schema.definitions) { for (const [name, def] of Object.entries(schema.definitions)) { @@ -153,13 +153,13 @@ function generateInnerMixin(className, fields) { for (const field of fields) { uniqueFields.set(field.name, field); } - + const fieldDefs = Array.from(uniqueFields.values()).map(field => { return ` @JsonProperty("${field.jsonProperty}") @JsonInclude(JsonInclude.Include.NON_NULL) abstract ${field.type} ${toGetterName(field.name)}();`; }).join('\n\n'); - + return ` public static abstract class ${className}Mixin { ${fieldDefs} }`; @@ -170,17 +170,17 @@ ${fieldDefs} */ function generateNullHandlingMixin() { const sortedClassNames = Array.from(mixinsNeeded.keys()).sort(); - + // Generate registration calls const registrations = sortedClassNames .map(className => ` om.addMixIn(${className}.class, ${className}Mixin.class);`) .join('\n'); - + // Generate inner mixin classes const innerClasses = sortedClassNames .map(className => generateInnerMixin(className, mixinsNeeded.get(className))) .join('\n\n'); - + return `package ${PACKAGE_NAME}; import com.fasterxml.jackson.annotation.JsonInclude; From e81705d4975078af2475545cccfee6bc728edda4 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 17 Dec 2025 14:59:45 +0000 Subject: [PATCH 27/65] 51 failing tests --- .../fdc3/proxy/support/TestMessaging.java | 5 +- .../responses/ChannelStateResponse.java | 200 ++++++++++++++++-- .../fdc3/testing/steps/GenericSteps.java | 19 +- .../fdc3/testing/support/MatchingUtils.java | 2 +- .../finos/fdc3/testing/world/PropsWorld.java | 65 +++++- 5 files changed, 267 insertions(+), 24 deletions(-) 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 index 4789fb59..50159131 100644 --- 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 @@ -100,12 +100,13 @@ public int getTimeoutMs() { return 1000; } - public List> getAllPosts() { + public synchronized List> getAllPosts() { return allPosts; } @Override - public CompletionStage post(Map message) { + public synchronized CompletionStage post(Map message) { + System.out.println("Post: "+message.get("type")); allPosts.add(message); String type = (String) message.get("type"); 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 index a6ec4781..515c7d9f 100644 --- 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 @@ -3,9 +3,11 @@ */ package org.finos.fdc3.proxy.support.responses; +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; @@ -15,30 +17,197 @@ import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; /** - * Responds to getCurrentContext requests. + * 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 final Map> channelState; + private String channelId = null; + private final Map> listeners = new HashMap<>(); + private final Map> contextHistory; - public ChannelStateResponse(Map> channelState) { - this.channelState = channelState; + public ChannelStateResponse(Map> contextHistory) { + this.contextHistory = contextHistory != null ? contextHistory : new HashMap<>(); } @Override public boolean filter(String messageType) { - return "getCurrentContextRequest".equals(messageType); + return "broadcastRequest".equals(messageType) || + "joinUserChannelRequest".equals(messageType) || + "leaveCurrentChannelRequest".equals(messageType) || + "getCurrentChannelRequest".equals(messageType) || + "addContextListenerRequest".equals(messageType) || + "contextListenerUnsubscribeRequest".equals(messageType) || + "getCurrentContextRequest".equals(messageType); } @Override @SuppressWarnings("unchecked") 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 channelId = (String) msgPayload.get("channelId"); + 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 = channelState.get(channelId); + List contexts = contextHistory.get(channel); Context foundContext = null; if (contexts != null && !contexts.isEmpty()) { @@ -51,23 +220,18 @@ public CompletionStage action(Map message, TestMessaging m } } } else { - // Return latest context - foundContext = contexts.get(contexts.size() - 1); + // Return first (most recent) context + foundContext = contexts.get(0); } } - Map payload = new HashMap<>(); - if (foundContext != null) { - payload.put("context", foundContext); - } + Map responsePayload = new HashMap<>(); + responsePayload.put("context", foundContext); Map response = new HashMap<>(); response.put("type", "getCurrentContextResponse"); response.put("meta", createResponseMeta(meta)); - response.put("payload", payload); - - scheduleReceive(messaging, response); - return CompletableFuture.completedFuture(null); + response.put("payload", responsePayload); + return response; } } - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index ebf4155d..d6c960b9 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -233,8 +233,23 @@ public void fieldIsValue(String field, String expected) { @Then("{string} is an error with message {string}") public void isAnErrorWithMessage(String field, String errorType) { Object value = handleResolve(field, world); - assertTrue(value instanceof Throwable); - assertEquals(errorType, ((Throwable) value).getMessage()); + assertTrue(value instanceof Throwable, "Expected a Throwable but got: " + value); + + // Extract the root cause message - exceptions may be wrapped multiple times + Throwable t = (Throwable) value; + String message = getRootCauseMessage(t); + assertEquals(errorType, message); + } + + /** + * Gets the message from the root cause of a potentially wrapped exception chain. + */ + private String getRootCauseMessage(Throwable t) { + Throwable root = t; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + return root.getMessage(); } @Then("{string} is an error") diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 0e97e4de..43c6fa32 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -55,7 +55,7 @@ private static Object extractFromWorld(Object world, String expression) { try { JXPathContext context = JXPathContext.newContext(world); context.setLenient(true); - String xpathName = "//" + expression.replaceAll("\\.", "/"); + String xpathName = "/" + expression.replaceAll("\\.", "/"); Object result = context.getValue(xpathName); // Unwrap Optional if needed if (result instanceof java.util.Optional) { diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java index ed7bea6f..1997f63b 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java @@ -16,8 +16,10 @@ package org.finos.fdc3.testing.world; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import io.cucumber.java.Scenario; import org.slf4j.Logger; @@ -27,7 +29,7 @@ * Cucumber World class that holds test state in a props map. * This is equivalent to the TypeScript PropsWorld class. */ -public class PropsWorld { +public class PropsWorld implements Map{ private static final Logger logger = LoggerFactory.getLogger(PropsWorld.class); @@ -132,4 +134,65 @@ public void attach(String data, String mediaType) { scenario.attach(data, mediaType, null); } } + + @Override + public int size() { + return props.size(); + } + + @Override + public boolean isEmpty() { + return props.size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return props.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return props.containsValue(value); + + } + + @Override + public Object put(String key, Object value) { + return props.put(key, value); + } + + @Override + public Object remove(Object key) { + return props.remove(key); + } + + @Override + public void putAll(Map m) { + props.putAll(m); + } + + @Override + public void clear() { + props.clear(); + } + + @Override + public Set keySet() { + return props.keySet(); + } + + @Override + public Collection values() { + return props.values(); + } + + @Override + public Set> entrySet() { + return props.entrySet(); + } + + @Override + public Object get(Object key) { + return props.get(key); + } } From c1b98f50617f3414a84752f53885668f6134c59c Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 17 Dec 2025 16:51:44 +0000 Subject: [PATCH 28/65] Work in progress fixing tests --- .../finos/fdc3/proxy/DesktopAgentProxy.java | 7 + .../org/finos/fdc3/proxy/apps/AppSupport.java | 11 ++ .../fdc3/proxy/apps/DefaultAppSupport.java | 23 ++++ .../intents/DefaultIntentResolution.java | 126 +++++++++++++++++- .../proxy/intents/DefaultIntentSupport.java | 4 +- fdc3-standard/pom.xml | 5 + .../java/org/finos/fdc3/api/DesktopAgent.java | 16 +++ .../org/finos/fdc3/api/channel/Channel.java | 22 ++- .../org/finos/fdc3/api/context/Context.java | 4 +- .../finos/fdc3/api/types/IntentResult.java | 9 +- 10 files changed, 209 insertions(+), 18 deletions(-) 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 index 38930c79..c615de94 100644 --- 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 @@ -155,6 +155,13 @@ public CompletionStage open(AppIdentifier app, Context context) { return apps.open(app, context); } + @Override + @Deprecated + @SuppressWarnings("deprecation") + public CompletionStage open(String name, Context context) { + return apps.open(name, context); + } + @Override public CompletionStage> findInstances(AppIdentifier app) { return apps.findInstances(app); 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 index 4c65e4a0..5ed3b600 100644 --- 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 @@ -54,6 +54,17 @@ public interface AppSupport { */ CompletionStage open(AppIdentifier app, Context context); + /** + * 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. * 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 index 31918c54..0d1e638e 100644 --- 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 @@ -141,6 +141,29 @@ public CompletionStage open(AppIdentifier app, Context context) { }); } + @Override + @Deprecated + public CompletionStage open(String name, Context context) { + // Create an AppIdentifier from the name string + AppIdentifier appIdentifier = new AppIdentifier() { + @Override + public String getAppId() { + return name; + } + + @Override + public Optional getInstanceId() { + return Optional.empty(); + } + + @Override + public Optional getDesktopAgent() { + return Optional.empty(); + } + }; + return open(appIdentifier, context); + } + @Override public CompletionStage getImplementationMetadata() { // Build typed request 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 index 490630be..85f93237 100644 --- 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 @@ -16,13 +16,19 @@ package org.finos.fdc3.proxy.intents; +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.DisplayMetadata; 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; +import org.finos.fdc3.proxy.channels.DefaultChannel; +import org.finos.fdc3.proxy.channels.DefaultPrivateChannel; /** * Default implementation of IntentResolution. @@ -30,16 +36,19 @@ public class DefaultIntentResolution implements IntentResolution { private final Messaging messaging; + private final long messageExchangeTimeout; private final CompletionStage resultPromise; private final AppIdentifier source; private final String intent; public DefaultIntentResolution( Messaging messaging, + long messageExchangeTimeout, CompletionStage resultPromise, AppIdentifier source, String intent) { this.messaging = messaging; + this.messageExchangeTimeout = messageExchangeTimeout; this.resultPromise = resultPromise; this.source = source; this.intent = intent; @@ -61,12 +70,121 @@ public Optional getVersion() { } @Override + @SuppressWarnings("unchecked") public CompletionStage getResult() { - return resultPromise.thenApply(result -> new IntentResult() { - @Override - public Object getValue() { - return result; + return resultPromise.thenApply(result -> { + // Return null when there's no result (void) + if (result == null) { + return (IntentResult) null; + } + + // The result from the Desktop Agent is a map with either 'context' or 'channel' key + if (result instanceof Map) { + Map resultMap = (Map) result; + + // Empty map means void result + if (resultMap.isEmpty()) { + return (IntentResult) null; + } + + // Check for context result + Object contextObj = resultMap.get("context"); + if (contextObj != null) { + if (contextObj instanceof Context) { + return (IntentResult) contextObj; + } else if (contextObj instanceof Map) { + return (IntentResult) Context.fromMap((Map) contextObj); + } + } + + // Check for channel result + Object channelObj = resultMap.get("channel"); + if (channelObj != null && channelObj instanceof Map) { + Map channelMap = (Map) channelObj; + return (IntentResult) createChannel(channelMap); + } } + + // If result is already a Context or Channel, return it directly + if (result instanceof IntentResult) { + return (IntentResult) result; + } + + return (IntentResult) null; }); } + + @SuppressWarnings("unchecked") + private Channel createChannel(Map channelMap) { + String id = (String) channelMap.get("id"); + Object typeObj = channelMap.get("type"); + + Channel.Type type; + if (typeObj instanceof Channel.Type) { + type = (Channel.Type) typeObj; + } else { + String typeStr = typeObj != null ? typeObj.toString() : null; + if ("user".equals(typeStr)) { + type = Channel.Type.User; + } else if ("app".equals(typeStr)) { + type = Channel.Type.App; + } else if ("private".equals(typeStr)) { + type = Channel.Type.Private; + } else { + type = Channel.Type.App; // default + } + } + + Map displayMetadataMap = (Map) channelMap.get("displayMetadata"); + final String dmName = displayMetadataMap != null ? (String) displayMetadataMap.get("name") : null; + final String dmColor = displayMetadataMap != null ? (String) displayMetadataMap.get("color") : null; + final String dmGlyph = displayMetadataMap != null ? (String) displayMetadataMap.get("glyph") : null; + DisplayMetadata displayMetadata = displayMetadataMap != null ? new DisplayMetadata() { + @Override + public Optional getName() { + return Optional.ofNullable(dmName); + } + + @Override + public Optional getColor() { + return Optional.ofNullable(dmColor); + } + + @Override + public Optional getGlyph() { + return Optional.ofNullable(dmGlyph); + } + } : null; + + // Create DisplayMetadata if present + DisplayMetadata dm = null; + if (dmName != null || dmColor != null || dmGlyph != null) { + final String finalName = dmName; + final String finalColor = dmColor; + final String finalGlyph = dmGlyph; + dm = new DisplayMetadata() { + @Override + public Optional getName() { + return Optional.ofNullable(finalName); + } + + @Override + public Optional getColor() { + return Optional.ofNullable(finalColor); + } + + @Override + public Optional getGlyph() { + return Optional.ofNullable(finalGlyph); + } + }; + } + + // Use existing channel implementations + if (type == Channel.Type.Private) { + return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); + } else { + return new DefaultChannel(messaging, messageExchangeTimeout, id, type, dm); + } + } } 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 index 8a650c4c..b53827ec 100644 --- 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 @@ -177,7 +177,7 @@ public CompletionStage raiseIntent(String intent, Context cont String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( - new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); + new DefaultIntentResolution(messaging, messageExchangeTimeout, resultPromise, source, resolvedIntent)); } }); } @@ -235,7 +235,7 @@ public CompletionStage raiseIntentForContext(Context context, String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( - new DefaultIntentResolution(messaging, resultPromise, source, resolvedIntent)); + new DefaultIntentResolution(messaging, messageExchangeTimeout, resultPromise, source, resolvedIntent)); } }); } diff --git a/fdc3-standard/pom.xml b/fdc3-standard/pom.xml index 2b7aec91..3e603025 100644 --- a/fdc3-standard/pom.xml +++ b/fdc3-standard/pom.xml @@ -54,6 +54,11 @@ 20210307 compile + + com.fasterxml.jackson.core + jackson-annotations + 2.16.0 + \ No newline at end of file diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index f939c524..fd2fa16b 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -85,6 +85,22 @@ default CompletionStage open(AppIdentifier app) { return open(app, null); } + /** + * @deprecated version of `open` that launches an app by name rather than `AppIdentifier`. + * Provided for backwards compatibility with FDC3 standard versions < 2.0. + */ + @Deprecated + CompletionStage open(String name, Context context); + + /** + * @deprecated version of `open` that launches an app by name rather than `AppIdentifier`. + * Provided for backwards compatibility with FDC3 standard versions < 2.0. + */ + @Deprecated + default CompletionStage open(String name) { + return open(name, null); + } + /** * Find out more information about a particular intent by passing its name, and * optionally its context and/or a desired result context type. diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java index 614c72e2..bc930196 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java @@ -39,9 +39,25 @@ public interface Channel extends IntentResult { * Can be "user", "app" or "private". */ enum Type { - User, - App, - Private, + User("user"), + App("app"), + Private("private"); + + private final String value; + + Type(String value) { + this.value = value; + } + + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } } Type getType(); diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java index e1d1fdee..4e752e6c 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/context/Context.java @@ -19,6 +19,8 @@ import java.util.HashMap; import java.util.Map; +import org.finos.fdc3.api.types.IntentResult; + /** * The base FDC3 Context type. * @@ -33,7 +35,7 @@ * by more specific type definitions (standardized or custom) to provide the structure and * properties shared by all FDC3 context data types. */ -public class Context extends HashMap { +public class Context extends HashMap implements IntentResult { public Context() { } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java index 2deef0e7..0656b300 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentResult.java @@ -22,14 +22,7 @@ /** * Describes results that an {@link IntentHandler} may optionally return that should be communicated back to the app that raised the * intent, via the {@link IntentResolution}. Represented as a union type in TypeScript, however in Java it is a marker interface - * implemented by {@link Context} and {@link Channel} + * implemented by {@link Context} and {@link Channel}. */ public interface IntentResult { - /** - * Get the underlying value of this result. - * This may be a Context, Channel, or null. - */ - default Object getValue() { - return this; - } } From a7981e8981aa762a73e5d79b9e7fcac9a882a163 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Wed, 17 Dec 2025 18:34:38 +0000 Subject: [PATCH 29/65] Removed duplicate classes --- TODO.md | 1 + .../fdc3/proxy/apps/DefaultAppSupport.java | 320 ++---------------- .../fdc3/proxy/channels/DefaultChannel.java | 17 + .../proxy/channels/DefaultChannelSupport.java | 44 +-- .../intents/DefaultIntentResolution.java | 51 +-- .../proxy/intents/DefaultIntentSupport.java | 164 +-------- .../listeners/DefaultIntentListener.java | 18 +- .../proxy/messaging/AbstractMessaging.java | 6 +- .../finos/fdc3/proxy/steps/IntentSteps.java | 42 +-- .../proxy/support/SimpleIntentResolver.java | 30 +- .../fdc3/proxy/support/TestMessaging.java | 29 +- .../FindIntentByContextResponse.java | 4 +- .../support/responses/FindIntentResponse.java | 6 +- .../RaiseIntentForContextResponse.java | 14 +- .../responses/RaiseIntentResponse.java | 16 +- fdc3-schema/pom.xml | 60 +++- fdc3-schema/scripts/generate-mixins.js | 260 -------------- .../finos/fdc3/schema/NullHandlingMixin.java | 127 +------ .../finos/fdc3/api/metadata/AppIntent.java | 47 ++- .../finos/fdc3/api/metadata/AppMetadata.java | 150 ++++++-- .../fdc3/api/metadata/ContextMetadata.java | 4 +- .../fdc3/api/metadata/DisplayMetadata.java | 112 +++--- .../org/finos/fdc3/api/metadata/Icon.java | 65 +++- .../org/finos/fdc3/api/metadata/Image.java | 81 ++++- .../api/metadata/ImplementationMetadata.java | 173 ++++++++-- .../fdc3/api/metadata/IntentMetadata.java | 49 ++- .../finos/fdc3/api/types/AppIdentifier.java | 89 ++++- .../fdc3/testing/steps/GenericSteps.java | 38 ++- .../fdc3/testing/support/MatchingUtils.java | 13 +- 29 files changed, 861 insertions(+), 1169 deletions(-) delete mode 100644 fdc3-schema/scripts/generate-mixins.js diff --git a/TODO.md b/TODO.md index d83bf020..2dbc1f7c 100644 --- a/TODO.md +++ b/TODO.md @@ -2,3 +2,4 @@ - Remove all the wellington headers - Documentation in the main FDC3 repo. +- Channel Metadata (intent results) 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 index 0d1e638e..9b65e0a5 100644 --- 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 @@ -18,10 +18,8 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; @@ -29,8 +27,6 @@ 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.Icon; -import org.finos.fdc3.api.metadata.Image; import org.finos.fdc3.api.metadata.ImplementationMetadata; import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.proxy.Messaging; @@ -76,8 +72,9 @@ public CompletionStage> findInstances(AppIdentifier app) { return new ArrayList<>(); } + // AppMetadata extends AppIdentifier, so we can return directly return Arrays.stream(typedResponse.getPayload().getAppIdentifiers()) - .map(this::toApiAppIdentifier) + .map(am -> (AppIdentifier) am) .collect(Collectors.toList()); }); } @@ -106,7 +103,8 @@ public CompletionStage getAppMetadata(AppIdentifier app) { throw new RuntimeException(ResolveError.TargetAppUnavailable.toString()); } - return toApiAppMetadata(typedResponse.getPayload().getAppMetadata()); + // Schema now uses fdc3-standard AppMetadata directly + return typedResponse.getPayload().getAppMetadata(); }); } @@ -145,23 +143,7 @@ public CompletionStage open(AppIdentifier app, Context context) { @Deprecated public CompletionStage open(String name, Context context) { // Create an AppIdentifier from the name string - AppIdentifier appIdentifier = new AppIdentifier() { - @Override - public String getAppId() { - return name; - } - - @Override - public Optional getInstanceId() { - return Optional.empty(); - } - - @Override - public Optional getDesktopAgent() { - return Optional.empty(); - } - }; - return open(appIdentifier, context); + return open(new AppIdentifier(name), context); } @Override @@ -182,7 +164,8 @@ public CompletionStage getImplementationMetadata() { if (typedResponse.getPayload() != null && typedResponse.getPayload().getImplementationMetadata() != null) { - return toApiImplementationMetadata(typedResponse.getPayload().getImplementationMetadata()); + // Schema now uses fdc3-standard ImplementationMetadata directly + return typedResponse.getPayload().getImplementationMetadata(); } else { Logger.error("Invalid response from Desktop Agent to getInfo!"); return createUnknownImplementationMetadata(); @@ -194,285 +177,34 @@ public CompletionStage getImplementationMetadata() { private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); - schemaApp.setAppID(app.getAppId()); - app.getInstanceId().ifPresent(schemaApp::setInstanceID); + schemaApp.setAppID(app.getAppID()); + if (app.getInstanceID() != null) { + schemaApp.setInstanceID(app.getInstanceID()); + } return schemaApp; } private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { - String appId = schemaApp.getAppID(); - String instanceId = schemaApp.getInstanceID(); - String desktopAgent = schemaApp.getDesktopAgent(); - return new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - - @Override - public Optional getDesktopAgent() { - return Optional.ofNullable(desktopAgent); - } - }; - } - - private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppMetadata schemaApp) { - String appId = schemaApp.getAppID(); - String instanceId = schemaApp.getInstanceID(); - String desktopAgent = schemaApp.getDesktopAgent(); - return new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - - @Override - public Optional getDesktopAgent() { - return Optional.ofNullable(desktopAgent); - } - }; - } - - private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaMetadata) { - String appId = schemaMetadata.getAppID(); - String instanceId = schemaMetadata.getInstanceID(); - String desktopAgent = schemaMetadata.getDesktopAgent(); - String name = schemaMetadata.getName(); - String title = schemaMetadata.getTitle(); - String description = schemaMetadata.getDescription(); - String version = schemaMetadata.getVersion(); - String tooltip = schemaMetadata.getTooltip(); - String resultType = schemaMetadata.getResultType(); - - return new AppMetadata() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - - @Override - public Optional getDesktopAgent() { - return Optional.ofNullable(desktopAgent); - } - - @Override - public Optional getName() { - return Optional.ofNullable(name); - } - - @Override - public Optional getVersion() { - return Optional.ofNullable(version); - } - - @Override - public Optional> getInstanceMetadata() { - return Optional.empty(); - } - - @Override - public Optional getTitle() { - return Optional.ofNullable(title); - } - - @Override - public Optional getTooltip() { - return Optional.ofNullable(tooltip); - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } - - @Override - public Optional> getIcons() { - return Optional.empty(); - } - - @Override - public Optional> getScreenshots() { - return Optional.empty(); - } - - @Override - public Optional getResultType() { - return Optional.ofNullable(resultType); - } - }; - } - - private ImplementationMetadata toApiImplementationMetadata(org.finos.fdc3.schema.ImplementationMetadata schemaMetadata) { - String fdc3Version = schemaMetadata.getFdc3Version(); - String provider = schemaMetadata.getProvider(); - String providerVersion = schemaMetadata.getProviderVersion(); - org.finos.fdc3.schema.AppMetadata schemaAppMetadata = schemaMetadata.getAppMetadata(); - org.finos.fdc3.schema.OptionalFeatures schemaOptionalFeatures = schemaMetadata.getOptionalFeatures(); - - AppMetadata appMetadata = schemaAppMetadata != null ? toApiAppMetadata(schemaAppMetadata) : null; - - return new ImplementationMetadata() { - @Override - public String getFdc3Version() { - return fdc3Version; - } - - @Override - public String getProvider() { - return provider; - } - - @Override - public String getProviderVersion() { - return providerVersion; - } - - @Override - public AppMetadata getAppMetadata() { - return appMetadata; - } - - @Override - public OptionalFeatures getOptionalFeatures() { - if (schemaOptionalFeatures == null) { - return null; - } - return new OptionalFeatures() { - @Override - public boolean isOriginatingAppMetadata() { - return schemaOptionalFeatures.getOriginatingAppMetadata(); - } - - @Override - public boolean isUserChannelMembershipAPIs() { - return schemaOptionalFeatures.getUserChannelMembershipAPIs(); - } - - @Override - public boolean isDesktopAgentBridging() { - return schemaOptionalFeatures.getDesktopAgentBridging(); - } - }; - } - }; + return new AppIdentifier( + schemaApp.getAppID(), + schemaApp.getInstanceID(), + schemaApp.getDesktopAgent() + ); } private ImplementationMetadata createUnknownImplementationMetadata() { - return new ImplementationMetadata() { - @Override - public String getFdc3Version() { - return "unknown"; - } - - @Override - public String getProvider() { - return "unknown"; - } - - @Override - public String getProviderVersion() { - return null; - } - - @Override - public AppMetadata getAppMetadata() { - return new AppMetadata() { - @Override - public String getAppId() { - return "unknown"; - } - - @Override - public Optional getInstanceId() { - return Optional.of("unknown"); - } - - @Override - public Optional getDesktopAgent() { - return Optional.empty(); - } - - @Override - public Optional getName() { - return Optional.empty(); - } - - @Override - public Optional getVersion() { - return Optional.empty(); - } - - @Override - public Optional> getInstanceMetadata() { - return Optional.empty(); - } - - @Override - public Optional getTitle() { - return Optional.empty(); - } - - @Override - public Optional getTooltip() { - return Optional.empty(); - } - - @Override - public Optional getDescription() { - return Optional.empty(); - } - - @Override - public Optional> getIcons() { - return Optional.empty(); - } - - @Override - public Optional> getScreenshots() { - return Optional.empty(); - } - - @Override - public Optional getResultType() { - return Optional.empty(); - } - }; - } + ImplementationMetadata result = new ImplementationMetadata(); + result.setFdc3Version("unknown"); + result.setProvider("unknown"); - @Override - public OptionalFeatures getOptionalFeatures() { - return new OptionalFeatures() { - @Override - public boolean isOriginatingAppMetadata() { - return false; - } + AppMetadata appMetadata = new AppMetadata(); + appMetadata.setAppID("unknown"); + appMetadata.setInstanceID("unknown"); + result.setAppMetadata(appMetadata); - @Override - public boolean isUserChannelMembershipAPIs() { - return false; - } + ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); + result.setOptionalFeatures(optFeatures); - @Override - public boolean isDesktopAgentBridging() { - return false; - } - }; - } - }; + return result; } } 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 index deecf881..146ae93e 100644 --- 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 @@ -20,6 +20,9 @@ import java.util.Optional; import java.util.concurrent.CompletionStage; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; + import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.metadata.DisplayMetadata; @@ -34,10 +37,13 @@ */ 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 displayMetadataValue; public DefaultChannel( @@ -59,9 +65,15 @@ public String getId() { } @Override + @JsonIgnore public Type getType() { return type; } + + @JsonGetter("type") + public String getTypeValue() { + return type != null ? type.getValue() : null; + } @Override public Optional displayMetadata() { @@ -69,6 +81,7 @@ public Optional displayMetadata() { } @Override + @JsonIgnore public CompletionStage broadcast(Context context) { BroadcastRequest request = new BroadcastRequest(); request.setType(BroadcastRequestType.BROADCAST_REQUEST); @@ -86,11 +99,13 @@ public CompletionStage broadcast(Context context) { } @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); @@ -117,11 +132,13 @@ public CompletionStage> getCurrentContext(String contextType) } @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); 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 index d33156a1..36b9bf09 100644 --- 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 @@ -126,10 +126,9 @@ public CompletionStage getUserChannel() { } org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); - DisplayMetadata displayMetadata = toApiDisplayMetadata(schemaChannel.getDisplayMetadata()); - + // Schema now uses fdc3-standard DisplayMetadata directly return new DefaultChannel(messaging, messageExchangeTimeout, - schemaChannel.getID(), Channel.Type.User, displayMetadata); + schemaChannel.getID(), Channel.Type.User, schemaChannel.getDisplayMetadata()); }); } @@ -160,12 +159,10 @@ public CompletionStage> getUserChannels() { return userChannels; } + // Schema now uses fdc3-standard DisplayMetadata directly userChannels = Arrays.stream(typedResponse.getPayload().getUserChannels()) - .map(c -> { - DisplayMetadata displayMetadata = toApiDisplayMetadata(c.getDisplayMetadata()); - return (Channel) new DefaultChannel( - messaging, messageExchangeTimeout, c.getID(), Channel.Type.User, displayMetadata); - }) + .map(c -> (Channel) new DefaultChannel( + messaging, messageExchangeTimeout, c.getID(), Channel.Type.User, c.getDisplayMetadata())) .collect(Collectors.toList()); return userChannels; @@ -195,9 +192,8 @@ public CompletionStage getOrCreate(String id) { } org.finos.fdc3.schema.Channel schemaChannel = typedResponse.getPayload().getChannel(); - DisplayMetadata displayMetadata = toApiDisplayMetadata(schemaChannel.getDisplayMetadata()); - - return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, displayMetadata); + // Schema now uses fdc3-standard DisplayMetadata directly + return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, schemaChannel.getDisplayMetadata()); }); } @@ -284,29 +280,5 @@ Channel getCurrentChannelInternal() { // ============ Helper methods ============ - private DisplayMetadata toApiDisplayMetadata(org.finos.fdc3.schema.DisplayMetadata schemaDisplayMetadata) { - if (schemaDisplayMetadata == null) { - return null; - } - String name = schemaDisplayMetadata.getName(); - String color = schemaDisplayMetadata.getColor(); - String glyph = schemaDisplayMetadata.getGlyph(); - - return new DisplayMetadata() { - @Override - public java.util.Optional getName() { - return java.util.Optional.ofNullable(name); - } - - @Override - public java.util.Optional getColor() { - return java.util.Optional.ofNullable(color); - } - - @Override - public java.util.Optional getGlyph() { - return java.util.Optional.ofNullable(glyph); - } - }; - } + // Schema now uses fdc3-standard DisplayMetadata directly, no conversion 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 index 85f93237..fb570026 100644 --- 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 @@ -136,55 +136,20 @@ private Channel createChannel(Map channelMap) { } Map displayMetadataMap = (Map) channelMap.get("displayMetadata"); - final String dmName = displayMetadataMap != null ? (String) displayMetadataMap.get("name") : null; - final String dmColor = displayMetadataMap != null ? (String) displayMetadataMap.get("color") : null; - final String dmGlyph = displayMetadataMap != null ? (String) displayMetadataMap.get("glyph") : null; - DisplayMetadata displayMetadata = displayMetadataMap != null ? new DisplayMetadata() { - @Override - public Optional getName() { - return Optional.ofNullable(dmName); - } - - @Override - public Optional getColor() { - return Optional.ofNullable(dmColor); - } - - @Override - public Optional getGlyph() { - return Optional.ofNullable(dmGlyph); - } - } : null; - - // Create DisplayMetadata if present - DisplayMetadata dm = null; - if (dmName != null || dmColor != null || dmGlyph != null) { - final String finalName = dmName; - final String finalColor = dmColor; - final String finalGlyph = dmGlyph; - dm = new DisplayMetadata() { - @Override - public Optional getName() { - return Optional.ofNullable(finalName); - } - - @Override - public Optional getColor() { - return Optional.ofNullable(finalColor); - } - - @Override - public Optional getGlyph() { - return Optional.ofNullable(finalGlyph); - } - }; + DisplayMetadata displayMetadata = null; + if (displayMetadataMap != null) { + displayMetadata = new DisplayMetadata( + (String) displayMetadataMap.get("name"), + (String) displayMetadataMap.get("color"), + (String) displayMetadataMap.get("glyph") + ); } // Use existing channel implementations if (type == Channel.Type.Private) { return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); } else { - return new DefaultChannel(messaging, messageExchangeTimeout, id, type, dm); + return new DefaultChannel(messaging, messageExchangeTimeout, id, type, displayMetadata); } } } 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 index b53827ec..ae05c886 100644 --- 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 @@ -17,20 +17,14 @@ package org.finos.fdc3.proxy.intents; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; 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.metadata.Icon; -import org.finos.fdc3.api.metadata.Image; -import org.finos.fdc3.api.metadata.IntentMetadata; import org.finos.fdc3.api.metadata.IntentResolution; import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.IntentHandler; @@ -88,8 +82,9 @@ public CompletionStage findIntent(String intent, Context context, Str throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - AppIntent appIntent = toApiAppIntent(typedResponse.getPayload().getAppIntent()); - if (appIntent.getApps().isEmpty()) { + // 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()); } @@ -120,9 +115,8 @@ public CompletionStage> findIntentsByContext(Context context) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - return Arrays.stream(typedResponse.getPayload().getAppIntents()) - .map(this::toApiAppIntent) - .collect(Collectors.toList()); + // Schema types now use fdc3-standard AppIntent directly + return Arrays.asList(typedResponse.getPayload().getAppIntents()); }); } @@ -156,7 +150,7 @@ public CompletionStage raiseIntent(String intent, Context cont throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - org.finos.fdc3.schema.AppIntent schemaAppIntent = typedResponse.getPayload().getAppIntent(); + AppIntent schemaAppIntent = typedResponse.getPayload().getAppIntent(); org.finos.fdc3.schema.IntentResolution schemaIntentResolution = typedResponse.getPayload().getIntentResolution(); if (schemaAppIntent == null && schemaIntentResolution == null) { @@ -164,8 +158,7 @@ public CompletionStage raiseIntent(String intent, Context cont } if (schemaAppIntent != null) { - AppIntent appIntent = toApiAppIntent(schemaAppIntent); - return intentResolver.chooseIntent(List.of(appIntent), context) + return intentResolver.chooseIntent(List.of(schemaAppIntent), context) .thenCompose(choice -> { if (choice == null) { throw new RuntimeException(ResolveError.UserCancelled.toString()); @@ -211,7 +204,7 @@ public CompletionStage raiseIntentForContext(Context context, throw new RuntimeException(ResolveError.NoAppsFound.toString()); } - org.finos.fdc3.schema.AppIntent[] schemaAppIntents = typedResponse.getPayload().getAppIntents(); + AppIntent[] schemaAppIntents = typedResponse.getPayload().getAppIntents(); org.finos.fdc3.schema.IntentResolution schemaIntentResolution = typedResponse.getPayload().getIntentResolution(); if ((schemaAppIntents == null || schemaAppIntents.length == 0) && schemaIntentResolution == null) { @@ -219,9 +212,7 @@ public CompletionStage raiseIntentForContext(Context context, } if (schemaAppIntents != null && schemaAppIntents.length > 0) { - List appIntents = Arrays.stream(schemaAppIntents) - .map(this::toApiAppIntent) - .collect(Collectors.toList()); + List appIntents = Arrays.asList(schemaAppIntents); return intentResolver.chooseIntent(appIntents, context) .thenCompose(choice -> { @@ -250,31 +241,19 @@ public CompletionStage addIntentListener(String intent, IntentHandler private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); - schemaApp.setAppID(app.getAppId()); - app.getInstanceId().ifPresent(schemaApp::setInstanceID); + schemaApp.setAppID(app.getAppID()); + if (app.getInstanceID() != null) { + schemaApp.setInstanceID(app.getInstanceID()); + } return schemaApp; } private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { - String appId = schemaApp.getAppID(); - String instanceId = schemaApp.getInstanceID(); - String desktopAgent = schemaApp.getDesktopAgent(); - return new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - - @Override - public Optional getDesktopAgent() { - return Optional.ofNullable(desktopAgent); - } - }; + return new AppIdentifier( + schemaApp.getAppID(), + schemaApp.getInstanceID(), + schemaApp.getDesktopAgent() + ); } @SuppressWarnings("unchecked") @@ -293,111 +272,4 @@ private CompletionStage createResultPromise(String requestUuid) { return payload.get("intentResult"); }); } - - private AppIntent toApiAppIntent(org.finos.fdc3.schema.AppIntent schemaAppIntent) { - org.finos.fdc3.schema.IntentMetadata schemaIntent = schemaAppIntent.getIntent(); - org.finos.fdc3.schema.AppMetadata[] schemaApps = schemaAppIntent.getApps(); - - String intentName = schemaIntent.getName(); - String displayName = schemaIntent.getDisplayName(); - - IntentMetadata intent = new IntentMetadata() { - @Override - public String getName() { - return intentName; - } - - @Override - public String getDisplayName() { - return displayName != null ? displayName : intentName; - } - }; - - List apps = Arrays.stream(schemaApps) - .map(this::toApiAppMetadata) - .collect(Collectors.toList()); - - return new AppIntent() { - @Override - public IntentMetadata getIntent() { - return intent; - } - - @Override - public List getApps() { - return apps; - } - }; - } - - private AppMetadata toApiAppMetadata(org.finos.fdc3.schema.AppMetadata schemaApp) { - String appId = schemaApp.getAppID(); - String instanceId = schemaApp.getInstanceID(); - String desktopAgent = schemaApp.getDesktopAgent(); - String name = schemaApp.getName(); - String title = schemaApp.getTitle(); - String description = schemaApp.getDescription(); - - return new AppMetadata() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - - @Override - public Optional getDesktopAgent() { - return Optional.ofNullable(desktopAgent); - } - - @Override - public Optional getName() { - return Optional.ofNullable(name); - } - - @Override - public Optional getVersion() { - return Optional.empty(); - } - - @Override - public Optional> getInstanceMetadata() { - return Optional.empty(); - } - - @Override - public Optional getTitle() { - return Optional.ofNullable(title); - } - - @Override - public Optional getTooltip() { - return Optional.empty(); - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } - - @Override - public Optional> getIcons() { - return Optional.empty(); - } - - @Override - public Optional> getScreenshots() { - return Optional.empty(); - } - - @Override - public Optional getResultType() { - return Optional.empty(); - } - }; - } } 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 index 5ebd1433..9ae059da 100644 --- 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 @@ -92,25 +92,11 @@ public void action(Map message) { String sourceAppId = (String) sourceMap.get("appId"); String sourceInstanceId = (String) sourceMap.get("instanceId"); String sourceDesktopAgent = (String) sourceMap.get("desktopAgent"); + AppIdentifier source = new AppIdentifier(sourceAppId, sourceInstanceId, sourceDesktopAgent); contextMetadata = new ContextMetadata() { @Override public AppIdentifier getSource() { - return new AppIdentifier() { - @Override - public String getAppId() { - return sourceAppId; - } - - @Override - public java.util.Optional getInstanceId() { - return java.util.Optional.ofNullable(sourceInstanceId); - } - - @Override - public java.util.Optional getDesktopAgent() { - return java.util.Optional.ofNullable(sourceDesktopAgent); - } - }; + return source; } }; } 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 index 02a60df6..7403f39e 100644 --- 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 @@ -69,9 +69,11 @@ public AddContextListenerRequestMeta createMeta() { if (appIdentifier != null) { org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appIdentifier.getAppId()); + source.setAppID(appIdentifier.getAppID()); source.setDesktopAgent("testing-da"); - appIdentifier.getInstanceId().ifPresent(source::setInstanceID); + if (appIdentifier.getInstanceID() != null) { + source.setInstanceID(appIdentifier.getInstanceID()); + } meta.setSource(source); } return meta; 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 index af20438b..2dbcf6b1 100644 --- 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 @@ -28,6 +28,8 @@ 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.channels.DefaultChannel; +import org.finos.fdc3.proxy.channels.DefaultPrivateChannel; import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.world.CustomWorld; @@ -142,23 +144,38 @@ public void raiseIntentTimesOut() { @Given("Raise Intent returns an app channel") public void raiseIntentReturnsAppChannel() { - // Would need a Channel implementation TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); - // intentResult.setChannel(...); + 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(...); + 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(...); + intentResult.setChannel(new DefaultPrivateChannel( + world.getMessaging(), + world.getMessaging().getTimeoutMs(), + "result-channel" + )); world.getMessaging().setIntentResult(intentResult); } @@ -227,22 +244,7 @@ public void returnsAVoidPromise(String intentHandlerName) { } private AppIdentifier createAppIdentifier(String appId, String instanceId) { - return new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getDesktopAgent() { - return Optional.of("some-desktop-agent"); - } - - @Override - public Optional getInstanceId() { - return Optional.ofNullable(instanceId); - } - }; + return new AppIdentifier(appId, instanceId, "some-desktop-agent"); } private TestMessaging.IntentDetail createIntentDetail(AppIdentifier app, String intent, String context, String resultType) { 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 index 606c15ed..ffd8f275 100644 --- 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 @@ -16,9 +16,8 @@ package org.finos.fdc3.proxy.support; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -57,29 +56,16 @@ public CompletionStage chooseIntent(List appI // Select the first intent and first app AppIntent firstIntent = appIntents.get(0); IntentMetadata intent = firstIntent.getIntent(); - List apps = new ArrayList<>(firstIntent.getApps()); + // getApps() returns AppMetadata[] so convert to list + List apps = Arrays.asList(firstIntent.getApps()); AppMetadata firstApp = apps.get(0); // Create an AppIdentifier from the AppMetadata - final String appId = firstApp.getAppId(); - final Optional instanceId = firstApp.getInstanceId(); - final Optional desktopAgent = firstApp.getDesktopAgent(); - AppIdentifier appIdentifier = new AppIdentifier() { - @Override - public String getAppId() { - return appId; - } - - @Override - public Optional getInstanceId() { - return instanceId; - } - - @Override - public Optional getDesktopAgent() { - return desktopAgent; - } - }; + AppIdentifier appIdentifier = new AppIdentifier( + firstApp.getAppID(), + firstApp.getInstanceID(), + firstApp.getDesktopAgent() + ); // Create the resolution choice IntentResolutionChoice resolution = new IntentResolutionChoice( 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 index 50159131..f67b89d4 100644 --- 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 @@ -52,22 +52,7 @@ public class TestMessaging extends AbstractMessaging { private PossibleIntentResult intentResult; public TestMessaging(Map> channelState) { - super(new AppIdentifier() { - @Override - public String getAppId() { - return "cucumber-app"; - } - - @Override - public java.util.Optional getInstanceId() { - return java.util.Optional.of("cucumber-instance"); - } - - @Override - public java.util.Optional getDesktopAgent() { - return java.util.Optional.of("testing-da"); - } - }); + super(new AppIdentifier("cucumber-app", "cucumber-instance", "testing-da")); this.channelState = channelState != null ? channelState : new HashMap<>(); // Set up automatic responses for various message types @@ -161,8 +146,10 @@ public Map createResponseMeta() { meta.put("responseUuid", createUUID()); meta.put("timestamp", Instant.now().toString()); Map source = new HashMap<>(); - source.put("appId", getAppIdentifier().getAppId()); - getAppIdentifier().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + source.put("appId", getAppIdentifier().getAppID()); + if (getAppIdentifier().getInstanceID() != null) { + source.put("instanceId", getAppIdentifier().getInstanceID()); + } meta.put("source", source); return meta; } @@ -176,8 +163,10 @@ public Map createEventMeta() { meta.put("eventUuid", createUUID()); meta.put("timestamp", Instant.now().toString()); Map source = new HashMap<>(); - source.put("appId", getAppIdentifier().getAppId()); - getAppIdentifier().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + source.put("appId", getAppIdentifier().getAppID()); + if (getAppIdentifier().getInstanceID() != null) { + source.put("instanceId", getAppIdentifier().getInstanceID()); + } meta.put("source", source); return meta; } 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 index add16224..ba7f28b0 100644 --- 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 @@ -63,8 +63,8 @@ public CompletionStage action(Map message, TestMessaging m for (IntentDetail detail : matching) { if (intentName.equals(detail.getIntent()) && detail.getApp() != null) { Map app = new HashMap<>(); - app.put("appId", detail.getApp().getAppId()); - detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + app.put("appId", detail.getApp().getAppID()); + if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } apps.add(app); } } 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 index cc57c00f..4f660178 100644 --- 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 @@ -47,10 +47,10 @@ public CompletionStage action(Map message, TestMessaging m // Build app list List> apps = new ArrayList<>(); for (IntentDetail detail : matching) { - if (detail.getApp() != null && detail.getApp().getAppId() != null) { + if (detail.getApp() != null && detail.getApp().getAppID() != null) { Map app = new HashMap<>(); - app.put("appId", detail.getApp().getAppId()); - detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + app.put("appId", detail.getApp().getAppID()); + if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } apps.add(app); } } 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 index 5b869496..54663834 100644 --- 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 @@ -84,12 +84,12 @@ private List findMatchingIntents(TestMessaging messaging, String c 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())) { + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppID())) { matches = false; } if (matches && targetInstanceId != null && - detail.getApp().getInstanceId().isPresent() && - !targetInstanceId.equals(detail.getApp().getInstanceId().get())) { + detail.getApp().getInstanceID() != null && + !targetInstanceId.equals(detail.getApp().getInstanceID())) { matches = false; } } @@ -111,8 +111,8 @@ private Map createRaiseIntentForContextResponse(Map source = new HashMap<>(); - source.put("appId", detail.getApp().getAppId()); - detail.getApp().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + 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()); @@ -137,8 +137,8 @@ private Map createRaiseIntentForContextResponse(Map app = new HashMap<>(); - app.put("appId", detail.getApp().getAppId()); - detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + app.put("appId", detail.getApp().getAppID()); + if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } apps.add(app); } } 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 index 5daac605..b47e1a22 100644 --- 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 @@ -88,12 +88,12 @@ private List findMatchingIntents(TestMessaging messaging, String i 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())) { + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppID())) { matches = false; } if (matches && targetInstanceId != null && - detail.getApp().getInstanceId().isPresent() && - !targetInstanceId.equals(detail.getApp().getInstanceId().get())) { + detail.getApp().getInstanceID() != null && + !targetInstanceId.equals(detail.getApp().getInstanceID())) { matches = false; } } @@ -118,8 +118,8 @@ private Map createRaiseIntentResponse(Map meta, } 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()); - detail.getApp().getInstanceId().ifPresent(id -> source.put("instanceId", id)); + 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()); @@ -129,10 +129,10 @@ private Map createRaiseIntentResponse(Map meta, // Multiple apps found - return appIntent for disambiguation List> apps = new ArrayList<>(); for (IntentDetail detail : relevant) { - if (detail.getApp() != null && detail.getApp().getAppId() != null) { + if (detail.getApp() != null && detail.getApp().getAppID() != null) { Map app = new HashMap<>(); - app.put("appId", detail.getApp().getAppId()); - detail.getApp().getInstanceId().ifPresent(id -> app.put("instanceId", id)); + app.put("appId", detail.getApp().getAppID()); + if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } apps.add(app); } } diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index 184344cf..007e15b4 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -179,21 +179,69 @@ - + - fix-context-imports + delete-duplicate-classes generate-sources exec - /bin/sh + rm ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema - -c - rm -f Context.java ContextID.java ContextType.java 2>/dev/null; for f in *.java; do grep -q "private Context " "$f" && sed -i '' 's/^package org.finos.fdc3.schema;$/package org.finos.fdc3.schema;\ -import org.finos.fdc3.api.context.Context;/' "$f"; done || true + -f + Context.java + ContextID.java + ContextType.java + Icon.java + Image.java + DisplayMetadata.java + IntentMetadata.java + ImplementationMetadata.java + OptionalFeatures.java + AppMetadata.java + AppIntent.java + + 0 + 1 + + + + + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.3 + + + add-standard-imports + generate-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.*; + + + true + + MULTILINE + diff --git a/fdc3-schema/scripts/generate-mixins.js b/fdc3-schema/scripts/generate-mixins.js deleted file mode 100644 index 50e42c30..00000000 --- a/fdc3-schema/scripts/generate-mixins.js +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env node -/** - * Script to generate NullHandlingMixin.java for FDC3 schema types. - * - * This script reads JSON schema files and identifies fields that are: - * - NOT required (optional) - * - Do NOT allow null as a type - * - * For these fields, we need @JsonInclude(Include.NON_NULL) to omit them - * when null, otherwise JSON Schema validation fails. - * - * Usage: node generate-mixins.js [schema-dir] [output-dir] - */ - -const fs = require('fs'); -const path = require('path'); - -// Configuration -const SCHEMA_DIR = process.argv[2] || path.join(__dirname, '../target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas/api'); -const OUTPUT_DIR = process.argv[3] || path.join(__dirname, '../src/main/java/org/finos/fdc3/schema'); -const PACKAGE_NAME = 'org.finos.fdc3.schema'; - -// Track all mixins we need to generate -const mixinsNeeded = new Map(); // className -> Set of field names - -/** - * Check if a schema type allows null - */ -function allowsNull(schema) { - if (!schema) return false; - - // Check for explicit null type - if (schema.type === 'null') return true; - - // Check for array of types including null - if (Array.isArray(schema.type) && schema.type.includes('null')) return true; - - // Check for oneOf/anyOf with null - if (schema.oneOf) { - return schema.oneOf.some(s => s.type === 'null'); - } - if (schema.anyOf) { - return schema.anyOf.some(s => s.type === 'null'); - } - - return false; -} - -/** - * Convert schema name to Java class name - */ -function toJavaClassName(name) { - return name.charAt(0).toUpperCase() + name.slice(1); -} - -/** - * Convert property name to Java getter method name - */ -function toGetterName(propName) { - return 'get' + propName.charAt(0).toUpperCase() + propName.slice(1); -} - -/** - * Get the Java type for a schema property (simplified) - */ -function getJavaType(schema, propName) { - if (!schema) return 'Object'; - - // Handle $ref - just use Object for simplicity - if (schema.$ref) return 'Object'; - - const type = schema.type; - if (type === 'string') return 'String'; - if (type === 'integer') return 'Long'; - if (type === 'number') return 'Double'; - if (type === 'boolean') return 'Boolean'; - if (type === 'array') return 'Object'; - if (type === 'object') return 'Object'; - - return 'Object'; -} - -/** - * Process a schema definition and find optional non-nullable fields - */ -function processDefinition(name, schema, schemaFile) { - if (!schema || schema.type !== 'object' || !schema.properties) { - return; - } - - const required = new Set(schema.required || []); - const optionalNonNullFields = []; - - for (const [propName, propSchema] of Object.entries(schema.properties)) { - // Skip if required - if (required.has(propName)) continue; - - // Skip if allows null - if (allowsNull(propSchema)) continue; - - // Skip if it's just "true" (any type allowed) - if (propSchema === true) continue; - - // This field needs NON_NULL - optionalNonNullFields.push({ - name: propName, - type: getJavaType(propSchema, propName), - jsonProperty: propName - }); - } - - if (optionalNonNullFields.length > 0) { - const className = toJavaClassName(name); - if (!mixinsNeeded.has(className)) { - mixinsNeeded.set(className, []); - } - mixinsNeeded.get(className).push(...optionalNonNullFields); - } -} - -/** - * Process a schema file - */ -function processSchemaFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const schema = JSON.parse(content); - - // Process $defs - if (schema.$defs) { - for (const [name, def] of Object.entries(schema.$defs)) { - processDefinition(name, def, filePath); - } - } - - // Process definitions (older style) - if (schema.definitions) { - for (const [name, def] of Object.entries(schema.definitions)) { - processDefinition(name, def, filePath); - } - } - } catch (e) { - console.error(`Error processing ${filePath}: ${e.message}`); - } -} - -/** - * Generate a single inner mixin class - */ -function generateInnerMixin(className, fields) { - // Deduplicate fields by name - const uniqueFields = new Map(); - for (const field of fields) { - uniqueFields.set(field.name, field); - } - - const fieldDefs = Array.from(uniqueFields.values()).map(field => { - return ` @JsonProperty("${field.jsonProperty}") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract ${field.type} ${toGetterName(field.name)}();`; - }).join('\n\n'); - - return ` public static abstract class ${className}Mixin { -${fieldDefs} - }`; -} - -/** - * Generate the complete NullHandlingMixin.java file - */ -function generateNullHandlingMixin() { - const sortedClassNames = Array.from(mixinsNeeded.keys()).sort(); - - // Generate registration calls - const registrations = sortedClassNames - .map(className => ` om.addMixIn(${className}.class, ${className}Mixin.class);`) - .join('\n'); - - // Generate inner mixin classes - const innerClasses = sortedClassNames - .map(className => generateInnerMixin(className, mixinsNeeded.get(className))) - .join('\n\n'); - - return `package ${PACKAGE_NAME}; - -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 FILE - Do not edit manually! - * Regenerate with: node scripts/generate-mixins.js - * - * 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. - */ -public final class NullHandlingMixin { - - private NullHandlingMixin() { - // Utility class - } - - /** - * Register all mix-ins with the given ObjectMapper. - */ - public static void registerAll(ObjectMapper om) { -${registrations} - } - - // === Mix-in classes for each schema type === - -${innerClasses} -} -`; -} - -// Main execution -console.log('Scanning schema files in:', SCHEMA_DIR); - -// Check if schema directory exists -if (!fs.existsSync(SCHEMA_DIR)) { - console.error(`Schema directory not found: ${SCHEMA_DIR}`); - console.error('Run "mvn generate-sources" first to download schemas.'); - process.exit(1); -} - -// Read all schema files -const schemaFiles = fs.readdirSync(SCHEMA_DIR) - .filter(f => f.endsWith('.schema.json')) - .map(f => path.join(SCHEMA_DIR, f)); - -// Process api.schema.json which has shared definitions -const apiSchemaPath = path.join(SCHEMA_DIR, 'api.schema.json'); -if (fs.existsSync(apiSchemaPath)) { - processSchemaFile(apiSchemaPath); -} - -// Process each schema file -for (const file of schemaFiles) { - processSchemaFile(file); -} - -console.log(`\nFound ${mixinsNeeded.size} classes needing mixins:`); -for (const [className, fields] of mixinsNeeded) { - // Deduplicate - const uniqueFields = new Set(fields.map(f => f.name)); - console.log(` - ${className} (${uniqueFields.size} fields)`); -} - -// Generate the single NullHandlingMixin.java file -const outputPath = path.join(OUTPUT_DIR, 'NullHandlingMixin.java'); -const content = generateNullHandlingMixin(); - -fs.writeFileSync(outputPath, content); -console.log(`\nGenerated: ${outputPath}`); - -console.log('\nDone!'); 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 index c4d1f240..8e56807f 100644 --- a/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java +++ b/fdc3-schema/src/main/java/org/finos/fdc3/schema/NullHandlingMixin.java @@ -12,6 +12,10 @@ * 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 { @@ -21,92 +25,22 @@ private NullHandlingMixin() { /** * 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(AppIdentifier.class, AppIdentifierMixin.class); - om.addMixIn(AppMetadata.class, AppMetadataMixin.class); - om.addMixIn(ImplementationMetadata.class, ImplementationMetadataMixin.class); om.addMixIn(BroadcastEventPayload.class, BroadcastEventPayloadMixin.class); om.addMixIn(Channel.class, ChannelMixin.class); - om.addMixIn(DisplayMetadata.class, DisplayMetadataMixin.class); om.addMixIn(FindIntentRequestPayload.class, FindIntentRequestPayloadMixin.class); om.addMixIn(FindIntentsByContextRequestPayload.class, FindIntentsByContextRequestPayloadMixin.class); - om.addMixIn(Icon.class, IconMixin.class); - om.addMixIn(Image.class, ImageMixin.class); om.addMixIn(IntentEventPayload.class, IntentEventPayloadMixin.class); - om.addMixIn(IntentMetadata.class, IntentMetadataMixin.class); om.addMixIn(OpenRequestPayload.class, OpenRequestPayloadMixin.class); om.addMixIn(RaiseIntentForContextRequestPayload.class, RaiseIntentForContextRequestPayloadMixin.class); om.addMixIn(RaiseIntentRequestPayload.class, RaiseIntentRequestPayloadMixin.class); } - // === Mix-in classes for each schema type === - - public static abstract class AppIdentifierMixin { - @JsonProperty("instanceId") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getInstanceId(); - - @JsonProperty("desktopAgent") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getDesktopAgent(); - } - - public static abstract class AppMetadataMixin { - @JsonProperty("name") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getName(); - - @JsonProperty("version") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getVersion(); - - @JsonProperty("title") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getTitle(); - - @JsonProperty("tooltip") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getTooltip(); - - @JsonProperty("description") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getDescription(); - - @JsonProperty("icons") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract Object getIcons(); - - @JsonProperty("screenshots") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract Object getScreenshots(); - - @JsonProperty("resultType") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getResultType(); - - @JsonProperty("instanceId") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getInstanceId(); - - @JsonProperty("instanceMetadata") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract Object getInstanceMetadata(); - - @JsonProperty("desktopAgent") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getDesktopAgent(); - } - - public static abstract class ImplementationMetadataMixin { - @JsonProperty("optionalFeatures") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract Object getOptionalFeatures(); - - @JsonProperty("providerVersion") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getProviderVersion(); - } + // === Mix-in classes for schema types that need null handling === public static abstract class BroadcastEventPayloadMixin { @JsonProperty("originatingApp") @@ -120,20 +54,6 @@ public static abstract class ChannelMixin { abstract Object getDisplayMetadata(); } - public static abstract class DisplayMetadataMixin { - @JsonProperty("name") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getName(); - - @JsonProperty("color") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getColor(); - - @JsonProperty("glyph") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getGlyph(); - } - public static abstract class FindIntentRequestPayloadMixin { @JsonProperty("context") @JsonInclude(JsonInclude.Include.NON_NULL) @@ -150,42 +70,12 @@ public static abstract class FindIntentsByContextRequestPayloadMixin { abstract String getResultType(); } - public static abstract class IconMixin { - @JsonProperty("size") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getSize(); - - @JsonProperty("type") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getType(); - } - - public static abstract class ImageMixin { - @JsonProperty("size") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getSize(); - - @JsonProperty("type") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getType(); - - @JsonProperty("label") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getLabel(); - } - public static abstract class IntentEventPayloadMixin { @JsonProperty("originatingApp") @JsonInclude(JsonInclude.Include.NON_NULL) abstract Object getOriginatingApp(); } - public static abstract class IntentMetadataMixin { - @JsonProperty("displayName") - @JsonInclude(JsonInclude.Include.NON_NULL) - abstract String getDisplayName(); - } - public static abstract class OpenRequestPayloadMixin { @JsonProperty("context") @JsonInclude(JsonInclude.Include.NON_NULL) @@ -204,4 +94,3 @@ public static abstract class RaiseIntentRequestPayloadMixin { abstract Object getApp(); } } - diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java index ab770c50..047307e7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppIntent.java @@ -16,15 +16,48 @@ package org.finos.fdc3.api.metadata; -import java.util.Collection; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** - * An interface that relates an intent to apps + * An interface that relates an intent to apps. + * + * Used if a raiseIntent request requires additional resolution (e.g. by showing an intent + * resolver) before it can be handled. */ -public interface AppIntent { - /** Details of the intent whose relationship to resolving applications is being described. */ - public IntentMetadata getIntent(); +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AppIntent { + + private AppMetadata[] apps; + private IntentMetadata intent; + + /** + * Default constructor for Jackson deserialization. + */ + public AppIntent() { + } + + /** + * Details of applications that can resolve the intent. + */ + @JsonProperty("apps") + public AppMetadata[] getApps() { + return apps; + } + + public void setApps(AppMetadata[] apps) { + this.apps = apps; + } + + /** + * Details of the intent whose relationship to resolving applications is being described. + */ + @JsonProperty("intent") + public IntentMetadata getIntent() { + return intent; + } - /** Details of applications that can resolve the intent. */ - public Collection getApps(); + public void setIntent(IntentMetadata intent) { + this.intent = intent; + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java index a4256494..f4d80995 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java @@ -18,48 +18,140 @@ import java.util.Collection; import java.util.Map; -import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import org.finos.fdc3.api.types.AppIdentifier; /** - * 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. + * 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. * - * The 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. + * The 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. * - * Note that as `AppMetadata` instances are also `AppIdentifiers` they may be passed to the `app` argument of `fdc3.open`, `fdc3.raiseIntent` etc. + * Note that as `AppMetadata` instances are also `AppIdentifiers` they may be passed to the `app` argument + * of `fdc3.open`, `fdc3.raiseIntent` etc. */ -public interface AppMetadata extends AppIdentifier -{ - /** - The 'friendly' app name. - This field was used with the `open` and `raiseIntent` calls in FDC3 <2.0, which now require an `AppIdentifier` wth `appId` set. - Note that for display purposes the `title` field should be used, if set, in preference to this field. - */ - public Optional getName(); +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AppMetadata extends AppIdentifier { + + private String name; + private String version; + private Map instanceMetadata; + private String title; + private String tooltip; + private String description; + private Collection icons; + private Collection screenshots; + private String resultType; + + /** + * Default constructor for Jackson deserialization. + */ + public AppMetadata() { + } + + /** + * The 'friendly' app name. + * This field was used with the `open` and `raiseIntent` calls in FDC3 <2.0, which now require an + * `AppIdentifier` with `appId` set. + * Note that for display purposes the `title` field should be used, if set, in preference to this field. + */ + @JsonProperty("name") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** The Version of the application. */ + @JsonProperty("version") + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + /** + * 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. + */ + @JsonProperty("instanceMetadata") + public Map getInstanceMetadata() { + return instanceMetadata; + } + + public void setInstanceMetadata(Map instanceMetadata) { + this.instanceMetadata = instanceMetadata; + } + + /** A more user-friendly application title that can be used to render UI elements. */ + @JsonProperty("title") + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + /** A tooltip for the application that can be used to render UI elements. */ + @JsonProperty("tooltip") + public String getTooltip() { + return tooltip; + } + + public void setTooltip(String tooltip) { + this.tooltip = tooltip; + } - /** The Version of the application. */ - public Optional getVersion(); + /** A longer, multi-paragraph description for the application that could include markup. */ + @JsonProperty("description") + public String getDescription() { + return 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. */ - public Optional> getInstanceMetadata(); + public void setDescription(String description) { + this.description = description; + } - /** A more user-friendly application title that can be used to render UI elements */ - public Optional getTitle(); + /** A list of icon URLs for the application that can be used to render UI elements. */ + @JsonProperty("icons") + public Collection getIcons() { + return icons; + } - /** A tooltip for the application that can be used to render UI elements */ - public Optional getTooltip(); + public void setIcons(Collection icons) { + this.icons = icons; + } - /** A longer, multi-paragraph description for the application that could include markup */ - public Optional getDescription(); + /** Images representing the app in common usage scenarios that can be used to render UI elements. */ + @JsonProperty("screenshots") + public Collection getScreenshots() { + return screenshots; + } - /** A list of icon URLs for the application that can be used to render UI elements - * @return*/ - public Optional> getIcons(); + public void setScreenshots(Collection screenshots) { + this.screenshots = screenshots; + } - /** Images representing the app in common usage scenarios that can be used to render UI elements */ - public Optional> getScreenshots(); + /** + * 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<fdc3.instrument>"). + */ + @JsonProperty("resultType") + public String getResultType() { + return resultType; + } - /** 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"). */ - public Optional getResultType(); + public void setResultType(String resultType) { + this.resultType = resultType; + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index 7a20fb93..92bfe92e 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -34,12 +34,12 @@ public interface ContextMetadata { /** Get the source app ID. Convenience method. */ default String getSourceAppId() { AppIdentifier source = getSource(); - return source != null ? source.getAppId() : null; + return source != null ? source.getAppID() : null; } /** Get the source instance ID. Convenience method. */ default String getSourceInstanceId() { AppIdentifier source = getSource(); - return source != null ? source.getInstanceId().orElse(null) : null; + return source != null ? source.getInstanceID() : null; } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java index b05dab94..4c830ee3 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DisplayMetadata.java @@ -16,56 +16,86 @@ package org.finos.fdc3.api.metadata; -import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; /** - * A system channel will be global enough to have a presence across many apps. This gives us some hints - * to render them in a standard way. It is assumed it may have other properties too, but if it has these, - * this is their meaning. + * Channels may be visualized and selectable by users. DisplayMetadata may be used to + * provide hints on how to see them. + * For App channels, displayMetadata would typically not be present. + * + * A system channel will be global enough to have a presence across many apps. This gives us + * some hints to render them in a standard way. It is assumed it may have other properties too, + * but if it has these, this is their meaning. */ -public interface DisplayMetadata { - /** - * A user-readable name for this channel, e.g: `"Red"` - */ - public Optional getName(); +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DisplayMetadata { + + private String name; + private String color; + private String glyph; + + /** + * Default constructor for Jackson deserialization. + */ + public DisplayMetadata() { + } + + public DisplayMetadata(String name, String color, String glyph) { + this.name = name; + this.color = color; + this.glyph = glyph; + } + + /** + * A user-readable name for this channel, e.g: `"Red"`. + */ + @JsonProperty("name") + public String getName() { + return name; + } - /** - * The color that should be associated within this channel when displaying this channel in a UI, e.g: `0xFF0000`. - */ - public Optional getColor(); + public void setName(String name) { + this.name = name; + } - /** - * A URL of an image that can be used to display this channel - */ - public Optional getGlyph(); + /** + * The color that should be associated within this channel when displaying this channel in a + * UI, e.g: `0xFF0000`. + */ + @JsonProperty("color") + public String getColor() { + return color; + } - /** - * Create a DisplayMetadata from a Map. - */ - public static DisplayMetadata fromMap(Map map) { - if (map == null) { - return null; + public void setColor(String color) { + this.color = color; } - String name = (String) map.get("name"); - String color = (String) map.get("color"); - String glyph = (String) map.get("glyph"); - return new DisplayMetadata() { - @Override - public Optional getName() { - return Optional.ofNullable(name); - } - @Override - public Optional getColor() { - return Optional.ofNullable(color); - } + /** + * A URL of an image that can be used to display this channel. + */ + @JsonProperty("glyph") + public String getGlyph() { + return glyph; + } - @Override - public Optional getGlyph() { - return Optional.ofNullable(glyph); - } - }; - } + public void setGlyph(String glyph) { + this.glyph = glyph; + } + + /** + * Create a DisplayMetadata from a Map. + */ + public static DisplayMetadata fromMap(Map map) { + if (map == null) { + return null; + } + String name = (String) map.get("name"); + String color = (String) map.get("color"); + String glyph = (String) map.get("glyph"); + return new DisplayMetadata(name, color, glyph); + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java index 674b1431..9aec6714 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Icon.java @@ -16,15 +16,64 @@ package org.finos.fdc3.api.metadata; -import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; -public interface Icon { - /** The icon url */ - public String getSrc(); +/** + * Describes an Icon image that may be used to represent the application. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Icon { + + private String src; + private String size; + private String type; + + /** + * Default constructor for Jackson deserialization. + */ + public Icon() { + } + + public Icon(String src, String size, String type) { + this.src = src; + this.size = size; + this.type = type; + } + + /** + * The icon url. + */ + @JsonProperty("src") + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } + + /** + * The icon dimension, formatted as `x`. + */ + @JsonProperty("size") + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } - /** The icon dimension, formatted as `x`. */ - public Optional getSize(); + /** + * Icon media type. If not present the Desktop Agent may use the src file extension. + */ + @JsonProperty("type") + public String getType() { + return type; + } - /** Icon media type. If not present the Desktop Agent may use the src file extension. */ - public Optional getType(); + public void setType(String type) { + this.type = type; + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java index c5d260d7..a91433bf 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/Image.java @@ -16,18 +16,79 @@ package org.finos.fdc3.api.metadata; -import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; -public interface Image { - /** The image url. */ - public String getSrc(); +/** + * Describes an image file, typically a screenshot, that often represents the application in + * a common usage scenario. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Image { + + private String src; + private String size; + private String type; + private String label; + + /** + * Default constructor for Jackson deserialization. + */ + public Image() { + } + + public Image(String src, String size, String type, String label) { + this.src = src; + this.size = size; + this.type = type; + this.label = label; + } + + /** + * The image url. + */ + @JsonProperty("src") + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } + + /** + * The image dimension, formatted as `x`. + */ + @JsonProperty("size") + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + /** + * Image media type. If not present the Desktop Agent may use the src file extension. + */ + @JsonProperty("type") + public String getType() { + return type; + } - /** The image dimension, formatted as `x`. */ - public Optional getSize(); + public void setType(String type) { + this.type = type; + } - /** Image media type. If not present the Desktop Agent may use the src file extension. */ - public Optional getType(); + /** + * Caption for the image. + */ + @JsonProperty("label") + public String getLabel() { + return label; + } - /** Caption for the image. */ - public Optional getLabel(); + public void setLabel(String label) { + this.label = label; + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java index 8e19e1c9..27f8126d 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ImplementationMetadata.java @@ -16,45 +16,144 @@ package org.finos.fdc3.api.metadata; -import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** * Metadata relating to the FDC3 Desktop Agent implementation and its provider. */ -public interface ImplementationMetadata { - /** The version number of the FDC3 specification that the implementation provides. - * The string must be a numeric semver version, e.g. 1.2 or 1.2.1. - */ - String getFdc3Version(); - - /** The name of the provider of the Desktop Agent implementation (e.g. Finsemble, Glue42, OpenFin etc.). */ - String getProvider(); - - /** The version of the provider of the Desktop Agent implementation (e.g. 5.3.0). */ - String getProviderVersion(); - - /** The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`). */ - AppMetadata getAppMetadata(); - - /** Metadata indicating whether the Desktop Agent implements optional features of - * the Desktop Agent API. - */ - OptionalFeatures getOptionalFeatures(); - - /** Metadata indicating whether the Desktop Agent implements optional features of - * the Desktop Agent API. - */ - interface OptionalFeatures { - /** Used to indicate whether the exposure of 'originating app metadata' for - * context and intent messages is supported by the Desktop Agent.*/ - boolean isOriginatingAppMetadata(); - - /** Used to indicate whether the optional `fdc3.joinUserChannel`, - * `fdc3.getCurrentChannel` and `fdc3.leaveCurrentChannel` are implemented by - * the Desktop Agent.*/ - boolean isUserChannelMembershipAPIs(); - - /** Used to indicate whether Desktop Agent bridging is supported by the Desktop Agent. */ - boolean isDesktopAgentBridging(); - } +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ImplementationMetadata { + + private String fdc3Version; + private String provider; + private String providerVersion; + private AppMetadata appMetadata; + private OptionalFeatures optionalFeatures; + + /** + * Default constructor for Jackson deserialization. + */ + public ImplementationMetadata() { + } + + /** + * The version number of the FDC3 specification that the implementation provides. + * The string must be a numeric semver version, e.g. 1.2 or 1.2.1. + */ + @JsonProperty("fdc3Version") + public String getFdc3Version() { + return fdc3Version; + } + + public void setFdc3Version(String fdc3Version) { + this.fdc3Version = fdc3Version; + } + + /** + * The name of the provider of the Desktop Agent implementation (e.g. Finsemble, Glue42, OpenFin etc.). + */ + @JsonProperty("provider") + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } + + /** + * The version of the provider of the Desktop Agent implementation (e.g. 5.3.0). + */ + @JsonProperty("providerVersion") + public String getProviderVersion() { + return providerVersion; + } + + public void setProviderVersion(String providerVersion) { + this.providerVersion = providerVersion; + } + + /** + * The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`). + */ + @JsonProperty("appMetadata") + public AppMetadata getAppMetadata() { + return appMetadata; + } + + public void setAppMetadata(AppMetadata appMetadata) { + this.appMetadata = appMetadata; + } + + /** + * Metadata indicating whether the Desktop Agent implements optional features of + * the Desktop Agent API. + */ + @JsonProperty("optionalFeatures") + public OptionalFeatures getOptionalFeatures() { + return optionalFeatures; + } + + public void setOptionalFeatures(OptionalFeatures optionalFeatures) { + this.optionalFeatures = optionalFeatures; + } + + /** + * Metadata indicating whether the Desktop Agent implements optional features of + * the Desktop Agent API. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class OptionalFeatures { + + private boolean originatingAppMetadata; + private boolean userChannelMembershipAPIs; + private boolean desktopAgentBridging; + + /** + * Default constructor for Jackson deserialization. + */ + public OptionalFeatures() { + } + + /** + * Used to indicate whether the exposure of 'originating app metadata' for + * context and intent messages is supported by the Desktop Agent. + */ + @JsonProperty("OriginatingAppMetadata") + public boolean isOriginatingAppMetadata() { + return originatingAppMetadata; + } + + public void setOriginatingAppMetadata(boolean originatingAppMetadata) { + this.originatingAppMetadata = originatingAppMetadata; + } + + /** + * Used to indicate whether the optional `fdc3.joinUserChannel`, + * `fdc3.getCurrentChannel` and `fdc3.leaveCurrentChannel` are implemented by + * the Desktop Agent. + */ + @JsonProperty("UserChannelMembershipAPIs") + public boolean isUserChannelMembershipAPIs() { + return userChannelMembershipAPIs; + } + + public void setUserChannelMembershipAPIs(boolean userChannelMembershipAPIs) { + this.userChannelMembershipAPIs = userChannelMembershipAPIs; + } + + /** + * Used to indicate whether the experimental Desktop Agent Bridging + * feature is implemented by the Desktop Agent. + */ + @JsonProperty("DesktopAgentBridging") + public boolean isDesktopAgentBridging() { + return desktopAgentBridging; + } + + public void setDesktopAgentBridging(boolean desktopAgentBridging) { + this.desktopAgentBridging = desktopAgentBridging; + } + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java index eff3207e..37384be1 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentMetadata.java @@ -16,13 +16,50 @@ package org.finos.fdc3.api.metadata; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + /** - * Intent descriptor + * Metadata describing an Intent. */ -public interface IntentMetadata { - /** The unique name of the intent that can be invoked by the raiseIntent call */ - public String getName(); +@JsonInclude(JsonInclude.Include.NON_NULL) +public class IntentMetadata { + + private String name; + private String displayName; + + /** + * Default constructor for Jackson deserialization. + */ + public IntentMetadata() { + } + + public IntentMetadata(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + + /** + * The unique name of the intent that can be invoked by the raiseIntent call. + */ + @JsonProperty("name") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Display name for the intent. + */ + @JsonProperty("displayName") + public String getDisplayName() { + return displayName; + } - /** A friendly display name for the intent that should be used to render UI elements */ - public String getDisplayName(); + public void setDisplayName(String displayName) { + this.displayName = displayName; + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java index 7e06abae..49440d03 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java @@ -16,24 +16,85 @@ package org.finos.fdc3.api.types; -import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** - * 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. + * 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. * * Will always include at least an `appId` field, which uniquely identifies a specific app. * - * If the `instanceId` field is set then the `AppMetadata` object represents a specific instance of the application that may be addressed using that Id. + * If the `instanceId` field is set then the `AppIdentifier` object represents a specific instance + * of the application that may be addressed using that Id. */ -public interface AppIdentifier { - /** The unique application identifier located within a specific application directory instance. An example of an appId might be 'app@sub.root' */ - public String getAppId(); - - /** An optional instance identifier, indicating that this object represents a specific instance of the application described.*/ - public Optional getInstanceId(); - - /** - * Identifier of the desktop agent, used in bridging. - */ - public Optional getDesktopAgent(); +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AppIdentifier { + + private String appID; + private String instanceID; + private String desktopAgent; + + /** + * Default constructor for Jackson deserialization. + */ + public AppIdentifier() { + } + + @JsonCreator + public AppIdentifier( + @JsonProperty("appId") String appID, + @JsonProperty("instanceId") String instanceID, + @JsonProperty("desktopAgent") String desktopAgent) { + this.appID = appID; + this.instanceID = instanceID; + this.desktopAgent = desktopAgent; + } + + public AppIdentifier(String appID, String instanceID) { + this(appID, instanceID, null); + } + + public AppIdentifier(String appID) { + this(appID, null, null); + } + + /** + * The unique application identifier located within a specific application directory instance. + * An example of an appId might be 'app@sub.root'. + */ + @JsonProperty("appId") + public String getAppID() { + return appID; + } + + public void setAppID(String appID) { + this.appID = appID; + } + + /** + * An optional instance identifier, indicating that this object represents a specific instance + * of the application described. + */ + @JsonProperty("instanceId") + public String getInstanceID() { + return instanceID; + } + + public void setInstanceID(String instanceID) { + this.instanceID = instanceID; + } + + /** + * Identifier of the desktop agent, used in bridging. + */ + @JsonProperty("desktopAgent") + public String getDesktopAgent() { + return desktopAgent; + } + + public void setDesktopAgent(String desktopAgent) { + this.desktopAgent = desktopAgent; + } } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index d6c960b9..22236a65 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -432,12 +432,7 @@ private Object resolvePromiseWithTimeout(Object promise, long timeout, TimeUnit * Invoke a method on an object by name. */ private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { - Class[] paramTypes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - paramTypes[i] = args[i] != null ? args[i].getClass() : Object.class; - } - - Method method = findMethod(target.getClass(), methodName, args.length); + Method method = findMethod(target.getClass(), methodName, args.length, args); if (method == null) { throw new NoSuchMethodException("Method not found: " + methodName); } @@ -450,15 +445,38 @@ private Object invokeMethod(Object target, String methodName, Object... args) th } /** - * Find a method by name and parameter count. + * Find a method by name and parameter types. + * First tries to find an exact match, then falls back to compatible types. */ - private Method findMethod(Class clazz, String name, int paramCount) { + private Method findMethod(Class clazz, String name, int paramCount, Object... args) { + Method fallback = null; for (Method method : clazz.getMethods()) { if (method.getName().equals(name) && method.getParameterCount() == paramCount) { - return method; + // Check if parameter types are compatible + Class[] paramTypes = method.getParameterTypes(); + boolean matches = true; + for (int i = 0; i < paramCount; i++) { + if (args[i] != null && !paramTypes[i].isAssignableFrom(args[i].getClass())) { + matches = false; + break; + } + } + if (matches) { + return method; + } + if (fallback == null) { + fallback = method; + } } } - return null; + return fallback; + } + + /** + * Find a method by name and parameter count (for backwards compatibility). + */ + private Method findMethod(Class clazz, String name, int paramCount) { + return findMethod(clazz, name, paramCount, new Object[paramCount]); } /** diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index 43c6fa32..caa6ec55 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -53,7 +53,18 @@ private MatchingUtils() { private static Object extractFromWorld(Object world, String expression) { // Use JXPath to resolve the value from props try { - JXPathContext context = JXPathContext.newContext(world); + // For non-Map objects, convert to Map first using Jackson + // This ensures enums with @JsonValue are properly serialized + Object navigable = world; + if (!(world instanceof Map)) { + try { + navigable = objectMapper.convertValue(world, Map.class); + } catch (Exception e) { + // If conversion fails, try using JXPath directly on the object + } + } + + JXPathContext context = JXPathContext.newContext(navigable); context.setLenient(true); String xpathName = "/" + expression.replaceAll("\\.", "/"); Object result = context.getValue(xpathName); From 831516e1705f45aefadd14b054a8933f6666ffb0 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 11:06:56 +0000 Subject: [PATCH 30/65] Removed duplicate appIdentifier class --- .../fdc3/proxy/apps/DefaultAppSupport.java | 26 +++---------- .../proxy/intents/DefaultIntentSupport.java | 26 +++---------- .../proxy/messaging/AbstractMessaging.java | 12 +++--- .../proxy/support/responses/OpenResponse.java | 4 +- fdc3-schema/pom.xml | 4 +- .../finos/fdc3/api/types/AppIdentifier.java | 2 + .../fdc3/testing/steps/GenericSteps.java | 37 ++++++++++++++++--- 7 files changed, 54 insertions(+), 57 deletions(-) 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 index 9b65e0a5..a6206032 100644 --- 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 @@ -56,7 +56,7 @@ public CompletionStage> findInstances(AppIdentifier app) { request.setMeta(messaging.createMeta()); FindInstancesRequestPayload payload = new FindInstancesRequestPayload(); - payload.setApp(toSchemaAppIdentifier(app)); + payload.setApp(app); request.setPayload(payload); // Convert to Map for messaging @@ -87,7 +87,7 @@ public CompletionStage getAppMetadata(AppIdentifier app) { request.setMeta(messaging.createMeta()); GetAppMetadataRequestPayload payload = new GetAppMetadataRequestPayload(); - payload.setApp(toSchemaAppIdentifier(app)); + payload.setApp(app); request.setPayload(payload); // Convert to Map for messaging @@ -116,7 +116,7 @@ public CompletionStage open(AppIdentifier app, Context context) { request.setMeta(messaging.createMeta()); OpenRequestPayload payload = new OpenRequestPayload(); - payload.setApp(toSchemaAppIdentifier(app)); + payload.setApp(app); if (context != null) { payload.setContext(context); } @@ -135,7 +135,7 @@ public CompletionStage open(AppIdentifier app, Context context) { throw new RuntimeException(OpenError.AppNotFound.toString()); } - return toApiAppIdentifier(typedResponse.getPayload().getAppIdentifier()); + return typedResponse.getPayload().getAppIdentifier(); }); } @@ -174,23 +174,7 @@ public CompletionStage getImplementationMetadata() { } // ============ Conversion helpers ============ - - private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { - org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); - schemaApp.setAppID(app.getAppID()); - if (app.getInstanceID() != null) { - schemaApp.setInstanceID(app.getInstanceID()); - } - return schemaApp; - } - - private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { - return new AppIdentifier( - schemaApp.getAppID(), - schemaApp.getInstanceID(), - schemaApp.getDesktopAgent() - ); - } + // Schema now uses fdc3-standard AppIdentifier directly, no conversion needed private ImplementationMetadata createUnknownImplementationMetadata() { ImplementationMetadata result = new ImplementationMetadata(); 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 index ae05c886..aa16c959 100644 --- 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 @@ -133,7 +133,7 @@ public CompletionStage raiseIntent(String intent, Context cont payload.setIntent(intent); payload.setContext(context); if (app != null) { - payload.setApp(toSchemaAppIdentifier(app)); + payload.setApp(app); } request.setPayload(payload); @@ -166,7 +166,7 @@ public CompletionStage raiseIntent(String intent, Context cont return raiseIntent(intent, context, choice.getAppId()); }); } else { - AppIdentifier source = toApiAppIdentifier(schemaIntentResolution.getSource()); + AppIdentifier source = schemaIntentResolution.getSource(); String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( @@ -187,7 +187,7 @@ public CompletionStage raiseIntentForContext(Context context, RaiseIntentForContextRequestPayload payload = new RaiseIntentForContextRequestPayload(); payload.setContext(context); if (app != null) { - payload.setApp(toSchemaAppIdentifier(app)); + payload.setApp(app); } request.setPayload(payload); @@ -222,7 +222,7 @@ public CompletionStage raiseIntentForContext(Context context, return raiseIntent(choice.getIntent(), context, choice.getAppId()); }); } else { - AppIdentifier source = toApiAppIdentifier(schemaIntentResolution.getSource()); + AppIdentifier source = schemaIntentResolution.getSource(); String resolvedIntent = schemaIntentResolution.getIntent(); return java.util.concurrent.CompletableFuture.completedFuture( @@ -238,23 +238,7 @@ public CompletionStage addIntentListener(String intent, IntentHandler } // ============ Helper methods ============ - - private org.finos.fdc3.schema.AppIdentifier toSchemaAppIdentifier(AppIdentifier app) { - org.finos.fdc3.schema.AppIdentifier schemaApp = new org.finos.fdc3.schema.AppIdentifier(); - schemaApp.setAppID(app.getAppID()); - if (app.getInstanceID() != null) { - schemaApp.setInstanceID(app.getInstanceID()); - } - return schemaApp; - } - - private AppIdentifier toApiAppIdentifier(org.finos.fdc3.schema.AppIdentifier schemaApp) { - return new AppIdentifier( - schemaApp.getAppID(), - schemaApp.getInstanceID(), - schemaApp.getDesktopAgent() - ); - } + // Schema now uses fdc3-standard AppIdentifier directly, no conversion needed @SuppressWarnings("unchecked") private CompletionStage createResultPromise(String requestUuid) { 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 index 7403f39e..61dc0238 100644 --- 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 @@ -68,12 +68,12 @@ public AddContextListenerRequestMeta createMeta() { meta.setTimestamp(OffsetDateTime.now()); if (appIdentifier != null) { - org.finos.fdc3.schema.AppIdentifier source = new org.finos.fdc3.schema.AppIdentifier(); - source.setAppID(appIdentifier.getAppID()); - source.setDesktopAgent("testing-da"); - if (appIdentifier.getInstanceID() != null) { - source.setInstanceID(appIdentifier.getInstanceID()); - } + // Create a copy with desktopAgent set + AppIdentifier source = new AppIdentifier( + appIdentifier.getAppID(), + appIdentifier.getInstanceID(), + "testing-da" + ); meta.setSource(source); } return meta; 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 index b04a8d72..113bf900 100644 --- 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 @@ -38,7 +38,7 @@ public CompletionStage action(Map message, TestMessaging m IntentDetail found = null; for (IntentDetail detail : messaging.getIntentDetails()) { if (detail.getApp() != null && appId != null && - appId.equals(detail.getApp().getAppId())) { + appId.equals(detail.getApp().getAppID())) { found = detail; break; } @@ -46,7 +46,7 @@ public CompletionStage action(Map message, TestMessaging m if (found != null && found.getApp() != null) { Map appIdentifier = new HashMap<>(); - appIdentifier.put("appId", found.getApp().getAppId()); + appIdentifier.put("appId", found.getApp().getAppID()); appIdentifier.put("instanceId", "abc123"); payload.put("appIdentifier", appIdentifier); } else { diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index 007e15b4..1d41cb0c 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -202,6 +202,7 @@ OptionalFeatures.java AppMetadata.java AppIntent.java + AppIdentifier.java 0 @@ -235,7 +236,8 @@ package org.finos.fdc3.schema; import org.finos.fdc3.api.context.Context; -import org.finos.fdc3.api.metadata.*; +import org.finos.fdc3.api.metadata.*; +import org.finos.fdc3.api.types.AppIdentifier; true diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java index 49440d03..3ff4aad7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java @@ -78,6 +78,7 @@ public void setAppID(String appID) { * of the application described. */ @JsonProperty("instanceId") + @JsonInclude(JsonInclude.Include.NON_NULL) public String getInstanceID() { return instanceID; } @@ -90,6 +91,7 @@ public void setInstanceID(String instanceID) { * Identifier of the desktop agent, used in bridging. */ @JsonProperty("desktopAgent") + @JsonInclude(JsonInclude.Include.NON_NULL) public String getDesktopAgent() { return desktopAgent; } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 22236a65..92fa651f 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -32,6 +32,9 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.List; +import java.util.ArrayList; +import java.lang.reflect.Array; +import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -148,25 +151,47 @@ public void iReferToAs(String from, String to) { // ========== Array Matching Steps ========== + /** + * Convert an object to a List. Handles both arrays and Lists. + */ + private List toList(Object obj) { + if (obj == null) { + return Collections.emptyList(); + } + if (obj instanceof List) { + return (List) obj; + } + if (obj.getClass().isArray()) { + // Convert array to list + int length = Array.getLength(obj); + List list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(Array.get(obj, i)); + } + return list; + } + throw new IllegalArgumentException("Expected array or List, but got: " + obj.getClass().getName()); + } + @Then("{string} is an array of objects with the following contents") public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { - @SuppressWarnings("unchecked") - List data = (List) handleResolve(field, world); + Object resolved = handleResolve(field, world); + List data = toList(resolved); matchData(world, data, dt); } @Then("{string} is an array of objects with length {string}") public void isAnArrayOfObjectsWithLength(String field, String lengthField) { - @SuppressWarnings("unchecked") - List data = (List) handleResolve(field, world); + Object resolved = handleResolve(field, world); + List data = toList(resolved); int expectedLength = ((Number) handleResolve(lengthField, world)).intValue(); assertEquals(expectedLength, data.size()); } @Then("{string} is an array of strings with the following values") public void isAnArrayOfStringsWithValues(String field, DataTable dt) { - @SuppressWarnings("unchecked") - List data = (List) handleResolve(field, world); + Object resolved = handleResolve(field, world); + List data = toList(resolved); List> values = data.stream() .map(s -> Map.of("value", s)) .collect(Collectors.toList()); From 40837c0f9c9ad1c1ca45cdbe9c9c132304de99c1 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 11:10:34 +0000 Subject: [PATCH 31/65] fixing compiler warnings --- TODO.md | 1 + .../finos/fdc3/proxy/DesktopAgentProxy.java | 1 - .../proxy/channels/DefaultChannelSupport.java | 24 +++++++++++++++++-- .../heartbeat/DefaultHeartbeatSupport.java | 5 +++- .../finos/fdc3/proxy/steps/ChannelSteps.java | 9 +++---- .../finos/fdc3/proxy/steps/IntentSteps.java | 1 - .../org/finos/fdc3/proxy/steps/UtilSteps.java | 6 +++-- .../fdc3/proxy/support/TestMessaging.java | 20 ++++++++++++++-- .../responses/AddEventListenerResponse.java | 5 ++-- .../responses/ChannelStateResponse.java | 6 ++--- .../CreatePrivateChannelResponse.java | 5 ++-- .../DisconnectPrivateChannelResponse.java | 5 ++-- .../responses/FindInstancesResponse.java | 5 ++-- .../FindIntentByContextResponse.java | 5 ++-- .../support/responses/FindIntentResponse.java | 5 ++-- .../responses/GetAppMetadataResponse.java | 5 ++-- .../support/responses/GetInfoResponse.java | 5 ++-- .../responses/GetOrCreateChannelResponse.java | 5 ++-- .../responses/GetUserChannelsResponse.java | 6 ++--- .../responses/IntentResultResponse.java | 6 ++--- .../proxy/support/responses/OpenResponse.java | 5 ++-- .../RaiseIntentForContextResponse.java | 5 ++-- .../responses/RaiseIntentResponse.java | 5 ++-- .../responses/RegisterListenersResponse.java | 5 ++-- .../support/responses/ResponseSupport.java | 1 - .../UnsubscribeListenersResponse.java | 5 ++-- 26 files changed, 103 insertions(+), 53 deletions(-) diff --git a/TODO.md b/TODO.md index 2dbc1f7c..6a5d3eeb 100644 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,4 @@ - Remove all the wellington headers - Documentation in the main FDC3 repo. - Channel Metadata (intent results) +- Heartbeat interval 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 index c615de94..1050708d 100644 --- 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 @@ -157,7 +157,6 @@ public CompletionStage open(AppIdentifier app, Context context) { @Override @Deprecated - @SuppressWarnings("deprecation") public CompletionStage open(String name, Context context) { return apps.open(name, context); } 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 index 36b9bf09..5fb10169 100644 --- 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 @@ -27,14 +27,34 @@ 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.metadata.DisplayMetadata; 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.proxy.Messaging; import org.finos.fdc3.proxy.listeners.DesktopAgentEventListener; import org.finos.fdc3.proxy.util.Logger; -import org.finos.fdc3.schema.*; +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. 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 index 5a28cca2..9731b75e 100644 --- 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 @@ -26,7 +26,10 @@ 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.*; +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. 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 index 3a053194..2d8564ff 100644 --- 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 @@ -16,11 +16,13 @@ package org.finos.fdc3.proxy.steps; +import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static org.finos.fdc3.testing.support.MatchingUtils.matchData; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.metadata.ContextMetadata; @@ -28,18 +30,13 @@ 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.support.TestMessaging; import org.finos.fdc3.proxy.world.CustomWorld; -import org.finos.fdc3.proxy.support.SimpleChannelSelector; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static org.finos.fdc3.testing.support.MatchingUtils.matchData; - /** * Cucumber step definitions for channel-related tests. */ 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 index 2dbcf6b1..6b40b9be 100644 --- 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 @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Supplier; 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 index 175206d2..9d5702a7 100644 --- 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 @@ -16,12 +16,14 @@ 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; -import static org.junit.jupiter.api.Assertions.*; - /** * Cucumber step definitions for utility tests. */ 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 index f67b89d4..5e501069 100644 --- 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 @@ -27,14 +27,30 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import java.util.function.Predicate; import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.context.Context; 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.*; +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. 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 index 66f4d083..59e86e71 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -11,8 +14,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to addEventListener requests. */ 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 index 515c7d9f..59ba3137 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -14,8 +17,6 @@ import org.finos.fdc3.api.context.Context; import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Handles channel-related requests: broadcast, join, leave, getCurrentChannel, * addContextListener, contextListenerUnsubscribe, getCurrentContext. @@ -44,7 +45,6 @@ public boolean filter(String messageType) { } @Override - @SuppressWarnings("unchecked") public CompletionStage action(Map message, TestMessaging messaging) { String type = (String) message.get("type"); Map response = null; 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 index 1038ee28..b518a67e 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -11,8 +14,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to createPrivateChannel requests. */ 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 index f4e72f4b..763f45cc 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -10,8 +13,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to privateChannelDisconnect requests. */ 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 index 53744f7e..794d5c9b 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -12,8 +15,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to findInstances requests. */ 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 index ba7f28b0..31c8e658 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -15,8 +18,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to findIntentsByContext requests. */ 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 index 4f660178..b725ef4f 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -13,8 +16,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to findIntent requests. */ 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 index f0dc6fac..6d390ee6 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -10,8 +13,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to getAppMetadata requests. */ 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 index 3a2732d5..cd4f7901 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -10,8 +13,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to getInfo requests. */ 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 index 75883d2f..0404d1d4 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -11,8 +14,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to getOrCreateChannel requests. */ 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 index f701399e..5fa9d67a 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -10,11 +13,8 @@ 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 static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to getUserChannels requests. */ 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 index 392c9916..3b0830f2 100644 --- 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 @@ -3,18 +3,18 @@ */ 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.channel.Channel; import org.finos.fdc3.api.context.Context; import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to intentResult requests. */ 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 index 113bf900..2406ced2 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -11,8 +14,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to open requests. */ 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 index 54663834..1ca9173a 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -19,8 +22,6 @@ import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to raiseIntentForContext requests. */ 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 index b47e1a22..770b0e5c 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -17,8 +20,6 @@ import org.finos.fdc3.proxy.support.TestMessaging.IntentDetail; import org.finos.fdc3.proxy.support.TestMessaging.PossibleIntentResult; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to raiseIntent requests. */ 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 index dce1dad9..c7f292ee 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -11,8 +14,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to listener registration requests. */ 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 index 2f95ca56..f2656eaf 100644 --- 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 @@ -23,7 +23,6 @@ public class ResponseSupport { /** * Creates response metadata from request metadata. */ - @SuppressWarnings("unchecked") public static Map createResponseMeta(Map requestMeta) { Map meta = new HashMap<>(); meta.put("requestUuid", requestMeta.get("requestUuid")); 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 index 1cfefecb..e393471c 100644 --- 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 @@ -3,6 +3,9 @@ */ 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; @@ -10,8 +13,6 @@ import org.finos.fdc3.proxy.support.TestMessaging; -import static org.finos.fdc3.proxy.support.responses.ResponseSupport.*; - /** * Responds to listener unsubscribe requests. */ From 1d27a4f32b190456a05e447b93bb82f365cd7644 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 15:05:14 +0000 Subject: [PATCH 32/65] Intent Results --- .../fdc3/proxy/apps/DefaultAppSupport.java | 4 +- .../listeners/DefaultIntentListener.java | 142 +++++++++++++++--- .../proxy/messaging/AbstractMessaging.java | 4 +- .../finos/fdc3/proxy/steps/IntentSteps.java | 121 ++++++++++++--- .../finos/fdc3/proxy/support/ContextMap.java | 1 + .../proxy/support/SimpleIntentResolver.java | 4 +- .../fdc3/proxy/support/TestMessaging.java | 12 +- .../FindIntentByContextResponse.java | 12 +- .../support/responses/FindIntentResponse.java | 6 +- .../proxy/support/responses/OpenResponse.java | 4 +- .../RaiseIntentForContextResponse.java | 14 +- .../responses/RaiseIntentResponse.java | 16 +- .../fdc3/api/metadata/ContextMetadata.java | 37 +++-- .../finos/fdc3/api/types/AppIdentifier.java | 28 +++- .../finos/fdc3/api/types/IntentHandler.java | 4 +- .../fdc3/testing/steps/GenericSteps.java | 4 +- .../fdc3/testing/support/MatchingUtils.java | 32 ++-- 17 files changed, 322 insertions(+), 123 deletions(-) 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 index a6206032..a7527add 100644 --- 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 @@ -182,8 +182,8 @@ private ImplementationMetadata createUnknownImplementationMetadata() { result.setProvider("unknown"); AppMetadata appMetadata = new AppMetadata(); - appMetadata.setAppID("unknown"); - appMetadata.setInstanceID("unknown"); + appMetadata.setAppId("unknown"); + appMetadata.setInstanceId("unknown"); result.setAppMetadata(appMetadata); ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); 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 index 9ae059da..2804225e 100644 --- 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 @@ -16,14 +16,24 @@ 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.ContextMetadata; import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.IntentHandler; import org.finos.fdc3.proxy.Messaging; +import org.finos.fdc3.schema.IntentEvent; +import org.finos.fdc3.schema.IntentResult; +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. @@ -79,28 +89,118 @@ public boolean filter(Map message) { @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); - - // Create context metadata from the message - Map meta = (Map) message.get("meta"); - Map sourceMap = (Map) meta.get("source"); - - ContextMetadata contextMetadata = null; - if (sourceMap != null) { - String sourceAppId = (String) sourceMap.get("appId"); - String sourceInstanceId = (String) sourceMap.get("instanceId"); - String sourceDesktopAgent = (String) sourceMap.get("desktopAgent"); - AppIdentifier source = new AppIdentifier(sourceAppId, sourceInstanceId, sourceDesktopAgent); - contextMetadata = new ContextMetadata() { - @Override - public AppIdentifier getSource() { - return source; - } - }; + // Convert the message to typed IntentEvent + IntentEvent intentEvent = messaging.getConverter().convertValue(message, IntentEvent.class); + + Context context = intentEvent.getPayload().getContext(); + AppIdentifier originatingApp = intentEvent.getPayload().getOriginatingApp(); + + ContextMetadata contextMetadata = new ContextMetadata(); + contextMetadata.setSource(originatingApp); + + // Call the handler and get the result + CompletionStage> resultFuture = + handler.handleIntent(context, contextMetadata); + + // Handle the intent result + handleIntentResult(resultFuture, intentEvent); + } + + private void handleIntentResult( + CompletionStage> resultFuture, + IntentEvent intentEvent) { + + resultFuture.thenAccept(optionalResult -> { + IntentResultRequest request = createIntentResultRequest(optionalResult.orElse(null), intentEvent); + + // Convert to Map and send + Map requestMap = messaging.getConverter().toMap(request); + + 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, 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 IntentResultRequest createIntentResultRequest( + org.finos.fdc3.api.types.IntentResult apiResult, + 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 IntentResult convertIntentResult(org.finos.fdc3.api.types.IntentResult apiResult) { + IntentResult schemaResult = new 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; + } - handler.handleIntent(context, contextMetadata); + 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/messaging/AbstractMessaging.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/messaging/AbstractMessaging.java index 61dc0238..7b12838e 100644 --- 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 @@ -70,8 +70,8 @@ public AddContextListenerRequestMeta createMeta() { if (appIdentifier != null) { // Create a copy with desktopAgent set AppIdentifier source = new AppIdentifier( - appIdentifier.getAppID(), - appIdentifier.getInstanceID(), + appIdentifier.getAppId(), + appIdentifier.getInstanceId(), "testing-da" ); meta.setSource(source); 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 index 6b40b9be..08d0835f 100644 --- 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 @@ -21,16 +21,24 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; +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.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.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; @@ -205,41 +213,106 @@ public void isAIntentEventMessage(String field, String intent, String context) { public void pipesIntentTo(String intentHandlerName, String field) { List> intents = new ArrayList<>(); world.set(field, intents); - world.set(intentHandlerName, (BiConsumer) (context, metadata) -> { - Map item = new HashMap<>(); - item.put("context", context); - item.put("metadata", metadata); - intents.add(item); - }); + + 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) { - world.set(intentHandlerName, (Supplier) () -> { - Map id = new HashMap<>(); - id.put("in", "one"); - id.put("out", "two"); - return new Context("fdc3.returned-intent", null, id); - }); + 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) { - world.set(intentHandlerName, (Supplier>) () -> { - Map channel = new HashMap<>(); - channel.put("type", "private"); - channel.put("id", "some-channel-id"); - Map displayMetadata = new HashMap<>(); - displayMetadata.put("color", "ochre"); - displayMetadata.put("name", "Some Channel"); - channel.put("displayMetadata", displayMetadata); - return channel; - }); + 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 Optional displayMetadata() { + return Optional.of(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; + } + + }; + return CompletableFuture.completedFuture(Optional.of(c)); + } + + }; + world.set(intentHandlerName, ih); } @Given("{string} returns a void promise") public void returnsAVoidPromise(String intentHandlerName) { - world.set(intentHandlerName, (Supplier) () -> null); + 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) { 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 index daaa7d4d..c49112f0 100644 --- 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 @@ -44,6 +44,7 @@ public final class ContextMap { // fdc3.unsupported Context unsupported = new Context("fdc3.unsupported"); + unsupported.put("bogus", true); CONTEXTS.put("fdc3.unsupported", unsupported); // fdc3.cancel-me 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 index ffd8f275..429015e4 100644 --- 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 @@ -62,8 +62,8 @@ public CompletionStage chooseIntent(List appI // Create an AppIdentifier from the AppMetadata AppIdentifier appIdentifier = new AppIdentifier( - firstApp.getAppID(), - firstApp.getInstanceID(), + firstApp.getAppId(), + firstApp.getInstanceId(), firstApp.getDesktopAgent() ); 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 index 5e501069..a965d51f 100644 --- 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 @@ -162,9 +162,9 @@ public Map createResponseMeta() { 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()); + source.put("appId", getAppIdentifier().getAppId()); + if (getAppIdentifier().getInstanceId() != null) { + source.put("instanceId", getAppIdentifier().getInstanceId()); } meta.put("source", source); return meta; @@ -179,9 +179,9 @@ public Map createEventMeta() { 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()); + source.put("appId", getAppIdentifier().getAppId()); + if (getAppIdentifier().getInstanceId() != null) { + source.put("instanceId", getAppIdentifier().getInstanceId()); } meta.put("source", source); return meta; 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 index 31c8e658..cfdfb1df 100644 --- 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 @@ -9,6 +9,8 @@ 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; @@ -46,7 +48,7 @@ public CompletionStage action(Map message, TestMessaging m } // Get unique intent names - Set uniqueIntents = new HashSet<>(); + Set uniqueIntents = new LinkedHashSet<>(); for (IntentDetail detail : matching) { if (detail.getIntent() != null) { uniqueIntents.add(detail.getIntent()); @@ -56,16 +58,16 @@ public CompletionStage action(Map message, TestMessaging m // Build appIntents List> appIntents = new ArrayList<>(); for (String intentName : uniqueIntents) { - Map intentInfo = new HashMap<>(); + 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 HashMap<>(); - app.put("appId", detail.getApp().getAppID()); - if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } + Map app = new LinkedHashMap<>(); + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } apps.add(app); } } 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 index b725ef4f..2cfd73fa 100644 --- 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 @@ -48,10 +48,10 @@ public CompletionStage action(Map message, TestMessaging m // Build app list List> apps = new ArrayList<>(); for (IntentDetail detail : matching) { - if (detail.getApp() != null && detail.getApp().getAppID() != null) { + 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()); } + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } apps.add(app); } } 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 index 2406ced2..6957401a 100644 --- 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 @@ -39,7 +39,7 @@ public CompletionStage action(Map message, TestMessaging m IntentDetail found = null; for (IntentDetail detail : messaging.getIntentDetails()) { if (detail.getApp() != null && appId != null && - appId.equals(detail.getApp().getAppID())) { + appId.equals(detail.getApp().getAppId())) { found = detail; break; } @@ -47,7 +47,7 @@ public CompletionStage action(Map message, TestMessaging m if (found != null && found.getApp() != null) { Map appIdentifier = new HashMap<>(); - appIdentifier.put("appId", found.getApp().getAppID()); + appIdentifier.put("appId", found.getApp().getAppId()); appIdentifier.put("instanceId", "abc123"); payload.put("appIdentifier", appIdentifier); } else { 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 index 1ca9173a..ac1767f1 100644 --- 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 @@ -85,12 +85,12 @@ private List findMatchingIntents(TestMessaging messaging, String c 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())) { + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppId())) { matches = false; } if (matches && targetInstanceId != null && - detail.getApp().getInstanceID() != null && - !targetInstanceId.equals(detail.getApp().getInstanceID())) { + detail.getApp().getInstanceId() != null && + !targetInstanceId.equals(detail.getApp().getInstanceId())) { matches = false; } } @@ -112,8 +112,8 @@ private Map createRaiseIntentForContextResponse(Map source = new HashMap<>(); - source.put("appId", detail.getApp().getAppID()); - if (detail.getApp().getInstanceID() != null) { source.put("instanceId", detail.getApp().getInstanceID()); } + 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()); @@ -138,8 +138,8 @@ private Map createRaiseIntentForContextResponse(Map app = new HashMap<>(); - app.put("appId", detail.getApp().getAppID()); - if (detail.getApp().getInstanceID() != null) { app.put("instanceId", detail.getApp().getInstanceID()); } + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } apps.add(app); } } 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 index 770b0e5c..af704354 100644 --- 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 @@ -89,12 +89,12 @@ private List findMatchingIntents(TestMessaging messaging, String i 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())) { + if (targetAppId != null && !targetAppId.equals(detail.getApp().getAppId())) { matches = false; } if (matches && targetInstanceId != null && - detail.getApp().getInstanceID() != null && - !targetInstanceId.equals(detail.getApp().getInstanceID())) { + detail.getApp().getInstanceId() != null && + !targetInstanceId.equals(detail.getApp().getInstanceId())) { matches = false; } } @@ -119,8 +119,8 @@ private Map createRaiseIntentResponse(Map meta, } 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()); } + 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()); @@ -130,10 +130,10 @@ private Map createRaiseIntentResponse(Map meta, // Multiple apps found - return appIntent for disambiguation List> apps = new ArrayList<>(); for (IntentDetail detail : relevant) { - if (detail.getApp() != null && detail.getApp().getAppID() != null) { + 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()); } + app.put("appId", detail.getApp().getAppId()); + if (detail.getApp().getInstanceId() != null) { app.put("instanceId", detail.getApp().getInstanceId()); } apps.add(app); } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index 92bfe92e..b210846a 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -16,30 +16,27 @@ package org.finos.fdc3.api.metadata; +import java.util.HashMap; + import org.finos.fdc3.api.types.AppIdentifier; /** - * Metadata relating to a context or intent and context received through the - * `addContextListener` and `addIntentListener` functions. - * - * @experimental Introduced in FDC3 2.0 and may be refined by further changes outside the normal FDC3 versioning policy. + * Metadata introduced in FDC3 2.0 + * */ -public interface ContextMetadata { - /** Identifier for the app instance that sent the context and/or intent. - * - * @experimental - */ - AppIdentifier getSource(); +public class ContextMetadata extends HashMap { + + public ContextMetadata() { + } - /** Get the source app ID. Convenience method. */ - default String getSourceAppId() { - AppIdentifier source = getSource(); - return source != null ? source.getAppID() : null; - } - /** Get the source instance ID. Convenience method. */ - default String getSourceInstanceId() { - AppIdentifier source = getSource(); - return source != null ? source.getInstanceID() : null; - } + public AppIdentifier getSource() { + AppIdentifier source = (AppIdentifier) this.get("source"); + return source; + } + + public void setSource(AppIdentifier source) { + this.put("source", source); + } + } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java index 3ff4aad7..74f3c093 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/AppIdentifier.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + /** * 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. @@ -65,11 +67,11 @@ public AppIdentifier(String appID) { * An example of an appId might be 'app@sub.root'. */ @JsonProperty("appId") - public String getAppID() { + public String getAppId() { return appID; } - public void setAppID(String appID) { + public void setAppId(String appID) { this.appID = appID; } @@ -79,11 +81,11 @@ public void setAppID(String appID) { */ @JsonProperty("instanceId") @JsonInclude(JsonInclude.Include.NON_NULL) - public String getInstanceID() { + public String getInstanceId() { return instanceID; } - public void setInstanceID(String instanceID) { + public void setInstanceId(String instanceID) { this.instanceID = instanceID; } @@ -99,4 +101,22 @@ public String getDesktopAgent() { public void setDesktopAgent(String desktopAgent) { this.desktopAgent = desktopAgent; } + + /** + * Creates an AppIdentifier from a Map representation. + * + * @param map the map containing appId, instanceId, and/or desktopAgent keys + * @return a new AppIdentifier instance, or null if the map is null + */ + public static AppIdentifier fromMap(Map map) { + if (map == null) { + return null; + } + + String appID = (String) map.get("appId"); + String instanceID = (String) map.get("instanceId"); + String desktopAgent = (String) map.get("desktopAgent"); + + return new AppIdentifier(appID, instanceID, desktopAgent); + } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java index 941d7472..a6acbb16 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java @@ -34,9 +34,9 @@ public interface IntentHandler { * * @param context the context event * @param contextMetadata optional metadata - * @return {@link Optional#empty()} if not result is required, or a {@link CompletionStage} that will be used to publish the + * @return A {@link CompletionStage} that will be used to publish the * intent result */ - Optional> handleIntent(Context context, ContextMetadata contextMetadata); + CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 92fa651f..4a64008a 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -184,8 +184,8 @@ public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { public void isAnArrayOfObjectsWithLength(String field, String lengthField) { Object resolved = handleResolve(field, world); List data = toList(resolved); - int expectedLength = ((Number) handleResolve(lengthField, world)).intValue(); - assertEquals(expectedLength, data.size()); + String amt = (String) handleResolve(lengthField, world); + assertEquals(Integer.parseInt(amt), data.size()); } @Then("{string} is an array of strings with the following values") diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java index caa6ec55..d21263bd 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -53,24 +55,28 @@ private MatchingUtils() { private static Object extractFromWorld(Object world, String expression) { // Use JXPath to resolve the value from props try { - // For non-Map objects, convert to Map first using Jackson - // This ensures enums with @JsonValue are properly serialized - Object navigable = world; - if (!(world instanceof Map)) { - try { - navigable = objectMapper.convertValue(world, Map.class); - } catch (Exception e) { - // If conversion fails, try using JXPath directly on the object - } - } - - JXPathContext context = JXPathContext.newContext(navigable); + JXPathContext context = JXPathContext.newContext(world); context.setLenient(true); String xpathName = "/" + expression.replaceAll("\\.", "/"); + // Convert .length to count(/path) for XPath + xpathName = xpathName.replaceAll("(/[^/]+)/length$", "count($1)"); + // Convert 0-based array indexes to 1-based for JXPath (e.g., [0] -> [1]) + Matcher matcher = Pattern.compile("\\[(\\d+)\\]").matcher(xpathName); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + int index = Integer.parseInt(matcher.group(1)); + matcher.appendReplacement(sb, "[" + (index + 1) + "]"); + } + matcher.appendTail(sb); + xpathName = sb.toString(); Object result = context.getValue(xpathName); // Unwrap Optional if needed if (result instanceof java.util.Optional) { - return ((java.util.Optional) result).orElse(null); + result = ((java.util.Optional) result).orElse(null); + } + // Convert numbers to rounded strings with 0 decimal places + if (result instanceof Number) { + return String.valueOf(Math.round(((Number) result).doubleValue())); } return result; } catch (JXPathNotFoundException e) { From f69a719b1cc5523b3e8df83e184835e0e0836760 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 15:18:26 +0000 Subject: [PATCH 33/65] Now throws error if trying to track the wrong event type --- .../listeners/DesktopAgentEventListener.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) 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 index d22a90e3..a2a60c5c 100644 --- 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 @@ -46,17 +46,35 @@ public DesktopAgentEventListener( "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 = toFDC3EventType(eventType); - if (fdc3EventType != null) { - payload.put("type", fdc3EventType.toValue()); - } + // 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; } @@ -91,7 +109,7 @@ private FDC3EventType toFDC3EventType(String eventType) { case "userChannelChanged": return FDC3EventType.USER_CHANNEL_CHANGED; default: - return null; + throw new RuntimeException("UnknownEventType"); } } } From 51d8e45c62910c3544b613ec892d42ce5e73b61b Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 15:37:34 +0000 Subject: [PATCH 34/65] fixed metadata channel query --- .../org/finos/fdc3/proxy/channels/DefaultChannel.java | 8 ++++---- .../test/java/org/finos/fdc3/proxy/steps/IntentSteps.java | 4 ++-- .../src/main/java/org/finos/fdc3/api/channel/Channel.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) 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 index 146ae93e..90ada513 100644 --- 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 @@ -44,7 +44,7 @@ public class DefaultChannel implements Channel { private final String id; private final Type type; @JsonIgnore - private final DisplayMetadata displayMetadataValue; + private final DisplayMetadata displayMetadata; public DefaultChannel( Messaging messaging, @@ -56,7 +56,7 @@ public DefaultChannel( this.messageExchangeTimeout = messageExchangeTimeout; this.id = id; this.type = type; - this.displayMetadataValue = displayMetadata; + this.displayMetadata = displayMetadata; } @Override @@ -76,8 +76,8 @@ public String getTypeValue() { } @Override - public Optional displayMetadata() { - return Optional.ofNullable(displayMetadataValue); + public DisplayMetadata getDisplayMetadata() { + return displayMetadata; } @Override 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 index 08d0835f..6385f5d8 100644 --- 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 @@ -266,8 +266,8 @@ public Type getType() { } @Override - public Optional displayMetadata() { - return Optional.of(dm); + public DisplayMetadata getDisplayMetadata() { + return dm; } @Override diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java index bc930196..819b27d7 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java @@ -66,7 +66,7 @@ public String toString() { * Channels may be visualized and selectable by users. DisplayMetadata may be used to provide hints on how to see them. * For App channels, displayMetadata would typically not be present. */ - Optional displayMetadata(); + DisplayMetadata getDisplayMetadata(); /** * Broadcasts a context on the channel. This function can be used without first joining the channel, allowing applications to broadcast on both App Channels and User Channels that they aren't a member of. From 9633e191e8e23070b0ffd166eae196c4b4d4dad8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 19 Dec 2025 18:37:47 +0000 Subject: [PATCH 35/65] Fixing Created enum for event type, fixed event handlers. --- .../proxy/channels/DefaultChannelSupport.java | 4 - .../proxy/channels/DefaultPrivateChannel.java | 78 +++++++++++++---- .../heartbeat/DefaultHeartbeatSupport.java | 3 +- .../proxy/listeners/AbstractListener.java | 27 +----- ... AbstractPrivateChannelEventListener.java} | 67 +++++---------- .../listeners/DesktopAgentEventListener.java | 19 ++++- ...PrivateChannelAddContextEventListener.java | 65 ++++++++++++++ ...PrivateChannelDisconnectEventListener.java | 60 +++++++++++++ .../PrivateChannelNullEventListener.java | 85 +++++++++++++++++++ ...rivateChannelUnsubscribeEventListener.java | 65 ++++++++++++++ .../proxy/listeners/RegisterableListener.java | 4 +- .../proxy/messaging/AbstractMessaging.java | 3 +- .../finos/fdc3/proxy/steps/ChannelSteps.java | 6 +- .../fdc3/api/channel/PrivateChannel.java | 43 ++++++++-- .../finos/fdc3/api/types/EventHandler.java | 2 +- .../org/finos/fdc3/api/types/FDC3Event.java | 57 ++++++++++--- .../org/finos/fdc3/api/types/Listener.java | 4 +- .../fdc3/testing/steps/GenericSteps.java | 17 ++-- 18 files changed, 483 insertions(+), 126 deletions(-) rename fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/{PrivateChannelEventListener.java => AbstractPrivateChannelEventListener.java} (61%) create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelAddContextEventListener.java create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelDisconnectEventListener.java create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelNullEventListener.java create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelUnsubscribeEventListener.java 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 index 5fb10169..c2a7b047 100644 --- 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 @@ -297,8 +297,4 @@ public CompletionStage addContextListener(ContextHandler handler, Stri Channel getCurrentChannelInternal() { return currentChannel; } - - // ============ Helper methods ============ - - // Schema now uses fdc3-standard DisplayMetadata directly, no conversion needed } 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 index 9e64366f..1a96c143 100644 --- 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 @@ -26,8 +26,14 @@ 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.PrivateChannelEventListener; +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.*; /** @@ -40,29 +46,69 @@ public DefaultPrivateChannel(Messaging messaging, long messageExchangeTimeout, S } @Override - public Listener onAddContextListener(Consumer> handler) { - PrivateChannelEventListener listener = new PrivateChannelEventListener( - messaging, messageExchangeTimeout, getId(), "addContextListener", - event -> handler.accept(Optional.ofNullable((String) event.getDetails()))); - listener.registerSync(); + 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 Listener 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 + listener.register(); return listener; } @Override - public Listener onUnsubsrcibe(Consumer> handler) { - PrivateChannelEventListener listener = new PrivateChannelEventListener( - messaging, messageExchangeTimeout, getId(), "unsubscribe", - event -> handler.accept(Optional.ofNullable((String) event.getDetails()))); - listener.registerSync(); + public Listener 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 + listener.register(); return listener; } @Override - public Listener onDisconnect(Runnable handler) { - PrivateChannelEventListener listener = new PrivateChannelEventListener( - messaging, messageExchangeTimeout, getId(), "disconnect", - event -> handler.run()); - listener.registerSync(); + public Listener 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))); + listener.register(); return listener; } 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 index 9731b75e..af69ad54 100644 --- 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 @@ -86,8 +86,9 @@ public CompletionStage register() { } @Override - public void unsubscribe() { + public CompletionStage unsubscribe() { messaging.unregister(id); + return CompletableFuture.completedFuture(null); } }; 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 index 346ae8ce..26217e71 100644 --- 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 @@ -130,32 +130,7 @@ public CompletionStage register() { } @Override - public void unsubscribe() { - if (this.id != null) { - messaging.unregister(this.id); - - // Send unsubscribe request to the server - 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); - - // Fire and forget - we don't wait for the response - messaging.>exchange(request, unsubscribeResponseType, messageExchangeTimeout); - } else { - throw new RuntimeException("This listener doesn't have an id and hence can't be removed!"); - } - } - - /** - * Unsubscribe asynchronously and wait for server acknowledgement. - * - * @return a CompletionStage that completes when unsubscribed - */ - public CompletionStage unsubscribeAsync() { + public CompletionStage unsubscribe() { if (this.id != null) { messaging.unregister(this.id); diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java similarity index 61% rename from fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java rename to fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java index 89906e27..295f63b4 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelEventListener.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/AbstractPrivateChannelEventListener.java @@ -17,26 +17,28 @@ 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.api.types.FDC3Event; import org.finos.fdc3.proxy.Messaging; import org.finos.fdc3.schema.PrivateChannelEventType; /** - * Event listener for private channel events. + * Abstract base class for private channel event listeners. * Extends AbstractListener to handle registration/unregistration. */ -public class PrivateChannelEventListener extends AbstractListener { +public abstract class AbstractPrivateChannelEventListener extends AbstractListener { - private final String channelId; - private final String eventType; + protected final String privateChannelId; + protected final List eventMessageTypes; + protected final String eventType; - public PrivateChannelEventListener( + protected AbstractPrivateChannelEventListener( Messaging messaging, long messageExchangeTimeout, - String channelId, + String privateChannelId, + List eventMessageTypes, String eventType, EventHandler handler) { super( @@ -48,7 +50,8 @@ public PrivateChannelEventListener( "privateChannelUnsubscribeEventListenerRequest", "privateChannelUnsubscribeEventListenerResponse" ); - this.channelId = channelId; + this.privateChannelId = privateChannelId; + this.eventMessageTypes = eventMessageTypes; this.eventType = eventType; } @@ -56,11 +59,10 @@ public PrivateChannelEventListener( protected Map buildSubscribeRequest() { Map request = new HashMap<>(); Map payload = new HashMap<>(); - payload.put("privateChannelId", channelId); + payload.put("privateChannelId", privateChannelId); PrivateChannelEventType pcEventType = toPrivateChannelEventType(eventType); - if (pcEventType != null) { - payload.put("listenerType", pcEventType.toValue()); - } + // 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; } @@ -69,7 +71,7 @@ protected Map buildSubscribeRequest() { @SuppressWarnings("unchecked") public boolean filter(Map message) { String type = (String) message.get("type"); - if (!getExpectedMessageType().equals(type)) { + if (!eventMessageTypes.contains(type)) { return false; } @@ -79,43 +81,11 @@ public boolean filter(Map message) { } String msgChannelId = (String) payload.get("privateChannelId"); - return channelId.equals(msgChannelId); - } - - private String getExpectedMessageType() { - if (eventType == null) { - return "privateChannelOnEvent"; - } - switch (eventType) { - case "addContextListener": - return "privateChannelOnAddContextListenerEvent"; - case "unsubscribe": - return "privateChannelOnUnsubscribeEvent"; - case "disconnect": - return "privateChannelOnDisconnectEvent"; - default: - return "privateChannelOnEvent"; - } + return privateChannelId.equals(msgChannelId); } @Override - @SuppressWarnings("unchecked") - public void action(Map message) { - Map payload = (Map) message.get("payload"); - FDC3Event> event = new FDC3Event<>(eventType, payload); - handler.handleEvent(event); - } - - /** - * Register synchronously. - */ - public void registerSync() { - try { - register().toCompletableFuture().get(); - } catch (Exception e) { - throw new RuntimeException("Failed to register listener", e); - } - } + public abstract void action(Map message); private PrivateChannelEventType toPrivateChannelEventType(String eventType) { if (eventType == null) { @@ -129,7 +99,8 @@ private PrivateChannelEventType toPrivateChannelEventType(String eventType) { case "disconnect": return PrivateChannelEventType.DISCONNECT; default: - return null; + throw new RuntimeException("Unsupported event type: " + eventType); } } } + 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 index a2a60c5c..c1803cac 100644 --- 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 @@ -72,7 +72,7 @@ private static void validateEventType(String eventType) { protected Map buildSubscribeRequest() { Map request = new HashMap<>(); Map payload = new HashMap<>(); - FDC3EventType fdc3EventType = toFDC3EventType(eventType); + 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); @@ -97,11 +97,12 @@ private String getExpectedMessageType() { @SuppressWarnings("unchecked") public void action(Map message) { Map payload = (Map) message.get("payload"); - FDC3Event> event = new FDC3Event<>(eventType, payload); + FDC3Event.Type fdc3EventType = toFDC3EventType(eventType); + FDC3Event event = new FDC3Event(fdc3EventType, payload); handler.handleEvent(event); } - private FDC3EventType toFDC3EventType(String eventType) { + private FDC3EventType toFDC3SchemaEventType(String eventType) { if (eventType == null) { return null; } @@ -112,4 +113,16 @@ private FDC3EventType toFDC3EventType(String eventType) { throw new RuntimeException("UnknownEventType"); } } + + private FDC3Event.Type toFDC3EventType(String eventType) { + if (eventType == null) { + return null; + } + switch (eventType) { + case "userChannelChanged": + return FDC3Event.Type.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..1309d6d9 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelAddContextEventListener.java @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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..508c8c1e --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelDisconnectEventListener.java @@ -0,0 +1,60 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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..4f8e6d62 --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelNullEventListener.java @@ -0,0 +1,85 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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..838c516b --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/listeners/PrivateChannelUnsubscribeEventListener.java @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 index 5aceea93..0b4e4680 100644 --- 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 @@ -57,7 +57,9 @@ public interface RegisterableListener { /** * Unsubscribe this listener from the messaging system. + * + * @return a CompletionStage that completes when unsubscribed */ - void unsubscribe(); + 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 index 7b12838e..f80a0000 100644 --- 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 @@ -119,8 +119,9 @@ public CompletionStage register() { } @Override - public void unsubscribe() { + public CompletionStage unsubscribe() { AbstractMessaging.this.unregister(id); + return CompletableFuture.completedFuture(null); } }; 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 index 2d8564ff..f7467e22 100644 --- 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 @@ -173,14 +173,14 @@ public void handleContext(Context context, ContextMetadata metadata) { @Given("{string} pipes events to {string}") public void pipesEventsTo(String typeHandlerName, String field) { - List> events = new ArrayList<>(); + List events = new ArrayList<>(); world.set(field, events); EventHandler eh = new EventHandler() { @Override - public void handleEvent(FDC3Event event) { - events.add(event); + public void handleEvent(FDC3Event event) { + events.add(event.getDetails()); } }; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java index 79ad06fd..161df162 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java @@ -16,9 +16,9 @@ package org.finos.fdc3.api.channel; -import java.util.Optional; -import java.util.function.Consumer; +import java.util.concurrent.CompletionStage; +import org.finos.fdc3.api.types.EventHandler; import org.finos.fdc3.api.types.Listener; /** @@ -31,25 +31,58 @@ * interface. */ public interface PrivateChannel extends Channel { + /** + * Register a handler for events from the PrivateChannel. Whenever the handler function + * is called it will be passed an event object with details related to the event. + * + *
+     * // any event type
+     * Listener listener = await myPrivateChannel.addEventListener(null, event -> {
+     *   System.out.println("Received event " + event.getType() + "\n\tDetails: " + event.getDetails());
+     * }).toCompletableFuture().join();
+     * 
+     * // listener for a specific event type
+     * Listener channelChangedListener = await myPrivateChannel.addEventListener(
+     *    "addContextListener",
+     *    event -> { ... }
+     * ).toCompletableFuture().join();
+     * 
+ * + * @param type If non-null, only events of the specified type will be received by the handler. + * Valid types are: "addContextListener", "unsubscribe", "disconnect", or null for all events. + * @param handler A function that events received will be passed to. + * @return A CompletionStage that resolves to a Listener object when the listener is successfully registered. + */ + CompletionStage addEventListener(String type, EventHandler handler); + /** * Adds a listener that will be called each time that the remote app invokes addContextListener on this channel. Desktop Agents * MUST call this for each invocation of addContextListener on this channel, including those that occurred before this handler * was registered (to prevent race conditions). + * + * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ - Listener onAddContextListener(Consumer> handler); + @Deprecated + Listener onAddContextListener(EventHandler handler); /** * Adds a listener that will be called whenever the remote app invokes Listener.unsubscribe() on a context listener that it * previously added. Desktop Agents MUST call this when disconnect() is called by the other party, for each listener that they * have added. + * + * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ - Listener onUnsubsrcibe(Consumer> handler); + @Deprecated + Listener onUnsubscribe(EventHandler handler); /** * Adds a listener that will be called when the remote app terminates, for example when its window is closed or because * disconnect was called. This is in addition to calls that will be made to onUnsubscribe listeners. + * + * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ - Listener onDisconnect(Runnable handler); + @Deprecated + Listener onDisconnect(EventHandler handler); /** * May be called to indicate that a participant will no longer interact with this channel. After this function has been called, diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java index d20f391e..42c08fd5 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/EventHandler.java @@ -29,6 +29,6 @@ public interface EventHandler { * * @param event the event object containing type and details */ - void handleEvent(FDC3Event event); + void handleEvent(FDC3Event event); } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java index a7ec007b..881c22ca 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java @@ -16,31 +16,66 @@ package org.finos.fdc3.api.types; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Type representing the event object passed to event handlers subscribed to * FDC3 events via the {@code addEventListener} method. * * Events will always include both {@code type} and {@code details} properties, * which describe the type of event and any additional details, respectively. - * - * @param The type of the details object for this event */ -public class FDC3Event { +public class FDC3Event { + + /** + * Enumeration of FDC3 event types. + */ + public enum Type { + ADD_CONTEXT_LISTENER("addContextListener"), + ON_UNSUBSCRIBE("onUnsubscribe"), + ON_DISCONNECT("onDisconnect"), + USER_CHANNEL_CHANGED("userChannelChanged"); + + private final String value; - private final String type; - private final T details; + Type(String value) { + this.value = value; + } - public FDC3Event(String type, T details) { + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static Type fromValue(String value) { + for (Type type : Type.values()) { + if (type.value.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown FDC3 event type: " + value); + } + + @Override + public String toString() { + return value; + } + } + + private final Type type; + private final Object details; + + public FDC3Event(Type type, Object details) { this.type = type; this.details = details; } /** - * Returns the type of the event. - * - * @return the event type string (e.g., "userChannelChanged") + * Returns the type of the event. */ - public String getType() { + public Type getType() { return type; } @@ -49,7 +84,7 @@ public String getType() { * * @return the event details */ - public T getDetails() { + public Object getDetails() { return details; } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java index c4b6911c..42eb477b 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/Listener.java @@ -16,9 +16,11 @@ package org.finos.fdc3.api.types; +import java.util.concurrent.CompletionStage; + public interface Listener { /** * Unsubscribe the listener object. */ - public void unsubscribe(); + CompletionStage unsubscribe(); } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 4a64008a..268edb40 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -43,6 +43,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.finos.fdc3.api.types.EventHandler; +import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.testing.world.PropsWorld; import com.networknt.schema.JsonSchema; @@ -288,11 +290,16 @@ public void isAnError(String field) { @Given("{string} is a invocation counter into {string}") public void isAnInvocationCounter(String handlerName, String counterField) { world.set(counterField, 0); - world.set(handlerName, (Runnable) () -> { - int amount = (Integer) world.get(counterField); - amount++; - world.set(counterField, amount); - }); + EventHandler eh = new EventHandler() { + + @Override + public void handleEvent(FDC3Event event) { + int amount = (Integer) world.get(counterField); + amount++; + world.set(counterField, amount); + } + }; + world.set(handlerName, eh); } // ========== Function Creation ========== From 172b91abb784ccf65412218d3c7dc27d282c36f0 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 10:41:05 +0000 Subject: [PATCH 36/65] Adding missing user channel support --- .../proxy/channels/DefaultChannelSupport.java | 12 +++++++++ .../DefaultUserChannelContextListener.java | 26 ++++++++++--------- .../channels/UserChannelContextListener.java | 4 +-- .../listeners/DefaultContextListener.java | 10 ++++--- .../finos/fdc3/proxy/steps/ChannelSteps.java | 19 +++++++++----- 5 files changed, 46 insertions(+), 25 deletions(-) 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 index c2a7b047..20c036f1 100644 --- 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 @@ -66,6 +66,7 @@ public class DefaultChannelSupport implements ChannelSupport { private final long messageExchangeTimeout; private List userChannels = null; private Channel currentChannel = null; + private final List userChannelListeners = new ArrayList<>(); public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelector, long messageExchangeTimeout) { this.messaging = messaging; @@ -284,12 +285,18 @@ public CompletionStage joinUserChannel(String id) { } channelSelector.updateChannel(id, channels); + + // Notify all user channel listeners of the channel change + for (UserChannelContextListener listener : userChannelListeners) { + listener.changeChannel(); + } }); } @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); } @@ -297,4 +304,9 @@ public CompletionStage addContextListener(ContextHandler handler, Stri 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/DefaultUserChannelContextListener.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/DefaultUserChannelContextListener.java index df43bceb..39409f86 100644 --- 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 @@ -46,29 +46,31 @@ public DefaultUserChannelContextListener( @Override public CompletionStage register() { - return super.register().thenCompose(v -> notifyChannelChange()); + return super.register().thenCompose(v -> { + changeChannel(); + return CompletableFuture.completedFuture(null); + }); } /** - * Called when the user channel changes. + * Called when the user channel changes. Gets the current context from the + * current channel and notifies the handler if there is one. */ @Override - public void changeChannel(Channel channel) { - super.changeChannel(channel); - } - - /** - * Notify the handler of current context after channel change. - */ - private CompletionStage notifyChannelChange() { + public void changeChannel() { Channel currentChannel = channelSupport.getCurrentChannelInternal(); if (currentChannel != null) { - return currentChannel.getCurrentContext(contextType) + currentChannel.getCurrentContext(contextType) .thenAccept(contextOpt -> { contextOpt.ifPresent(context -> handler.handleContext(context, null)); }); } - return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage unsubscribe() { + channelSupport.removeUserChannelListener(this); + return super.unsubscribe(); } @Override 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 index 42b479cd..7cc6fda2 100644 --- 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 @@ -31,8 +31,6 @@ public interface UserChannelContextListener extends Listener, RegisterableListen * 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. - * - * @param channel the new channel, or null if leaving channel */ - void changeChannel(Channel channel); + void changeChannel(); } 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 index 79ad8c5a..10e53c05 100644 --- 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 @@ -23,13 +23,12 @@ import org.finos.fdc3.api.context.Context; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.proxy.Messaging; -import org.finos.fdc3.proxy.channels.UserChannelContextListener; /** * Default implementation of a context listener. * Extends AbstractListener to handle registration/unregistration. */ -public class DefaultContextListener extends AbstractListener implements UserChannelContextListener { +public class DefaultContextListener extends AbstractListener { protected String channelId; protected final String contextType; @@ -65,7 +64,12 @@ public DefaultContextListener( this.messageType = messageType; } - @Override + /** + * 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; 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 index f7467e22..eac435a7 100644 --- 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 @@ -331,9 +331,9 @@ public Object invoke(Object... args) throws Exception { paramTypes[i] = args[i] != null ? args[i].getClass() : Object.class; } - java.lang.reflect.Method method = findMethod(target.getClass(), methodName, args.length); + java.lang.reflect.Method method = findMethod(target.getClass(), methodName, paramTypes); if (method == null) { - throw new NoSuchMethodException("Method not found: " + methodName); + throw new NoSuchMethodException("Method not found: " + methodName + " with parameter types: " + java.util.Arrays.toString(paramTypes)); } method.setAccessible(true); @@ -346,13 +346,18 @@ public Object invoke(Object... args) throws Exception { return result; } - private java.lang.reflect.Method findMethod(Class clazz, String name, int paramCount) { - for (java.lang.reflect.Method m : clazz.getMethods()) { - if (m.getName().equals(name) && m.getParameterCount() == paramCount) { - return m; + private java.lang.reflect.Method findMethod(Class clazz, String name, Class[] paramTypes) { + // Try public methods first (including inherited) + try { + return clazz.getMethod(name, paramTypes); + } catch (NoSuchMethodException e) { + // Try declared methods (including private/protected) + try { + return clazz.getDeclaredMethod(name, paramTypes); + } catch (NoSuchMethodException e2) { + return null; } } - return null; } } } From 1815e9a3fc906e12d8806567ffce9078fa5d69d4 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 11:00:12 +0000 Subject: [PATCH 37/65] Fixed issue with find method. --- .../finos/fdc3/proxy/steps/ChannelSteps.java | 24 +----- .../fdc3/testing/steps/GenericSteps.java | 85 ++++++++++++------- 2 files changed, 60 insertions(+), 49 deletions(-) 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 index eac435a7..4e9da592 100644 --- 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 @@ -19,6 +19,7 @@ import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; import static org.finos.fdc3.testing.support.MatchingUtils.matchData; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -31,6 +32,7 @@ import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.proxy.support.ContextMap; import org.finos.fdc3.proxy.world.CustomWorld; +import org.finos.fdc3.testing.steps.GenericSteps; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; @@ -326,14 +328,9 @@ public DestructuredMethod(Object target, String methodName) { } public Object invoke(Object... args) throws Exception { - Class[] paramTypes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - paramTypes[i] = args[i] != null ? args[i].getClass() : Object.class; - } - - java.lang.reflect.Method method = findMethod(target.getClass(), methodName, paramTypes); + java.lang.reflect.Method method = GenericSteps.findMethod(target.getClass(), methodName, args); if (method == null) { - throw new NoSuchMethodException("Method not found: " + methodName + " with parameter types: " + java.util.Arrays.toString(paramTypes)); + throw new NoSuchMethodException("Method not found: " + methodName); } method.setAccessible(true); @@ -346,19 +343,6 @@ public Object invoke(Object... args) throws Exception { return result; } - private java.lang.reflect.Method findMethod(Class clazz, String name, Class[] paramTypes) { - // Try public methods first (including inherited) - try { - return clazz.getMethod(name, paramTypes); - } catch (NoSuchMethodException e) { - // Try declared methods (including private/protected) - try { - return clazz.getDeclaredMethod(name, paramTypes); - } catch (NoSuchMethodException e2) { - return null; - } - } - } } } diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java index 268edb40..02309a03 100644 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java @@ -464,7 +464,7 @@ private Object resolvePromiseWithTimeout(Object promise, long timeout, TimeUnit * Invoke a method on an object by name. */ private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { - Method method = findMethod(target.getClass(), methodName, args.length, args); + Method method = findMethod(target.getClass(), methodName, args); if (method == null) { throw new NoSuchMethodException("Method not found: " + methodName); } @@ -476,39 +476,66 @@ private Object invokeMethod(Object target, String methodName, Object... args) th return resolvePromise(result); } - /** - * Find a method by name and parameter types. - * First tries to find an exact match, then falls back to compatible types. - */ - private Method findMethod(Class clazz, String name, int paramCount, Object... args) { - Method fallback = null; - for (Method method : clazz.getMethods()) { - if (method.getName().equals(name) && method.getParameterCount() == paramCount) { - // Check if parameter types are compatible - Class[] paramTypes = method.getParameterTypes(); - boolean matches = true; - for (int i = 0; i < paramCount; i++) { - if (args[i] != null && !paramTypes[i].isAssignableFrom(args[i].getClass())) { - matches = false; - break; - } - } - if (matches) { - return method; - } - if (fallback == null) { - fallback = method; + + public static Method findMethod( + Class targetClass, + String name, + Object... args + ) { + Method bestMatch = null; + + for (Method method : targetClass.getMethods()) { + if (!method.getName().equals(name)) continue; + + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != args.length) continue; + + if (isCompatible(paramTypes, args)) { + if (bestMatch == null || + isMoreSpecific(paramTypes, bestMatch.getParameterTypes())) { + bestMatch = method; } } } - return fallback; + + return bestMatch; } - /** - * Find a method by name and parameter count (for backwards compatibility). - */ - private Method findMethod(Class clazz, String name, int paramCount) { - return findMethod(clazz, name, paramCount, new Object[paramCount]); + private static boolean isCompatible(Class[] paramTypes, Object[] args) { + for (int i = 0; i < paramTypes.length; i++) { + if (args[i] == null) { + if (paramTypes[i].isPrimitive()) return false; + continue; + } + + Class argType = args[i].getClass(); + if (!wrap(paramTypes[i]).isAssignableFrom(argType)) { + return false; + } + } + return true; + } + + private static boolean isMoreSpecific(Class[] a, Class[] b) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i] && b[i].isAssignableFrom(a[i])) { + return true; + } + } + return false; + } + + private static Class wrap(Class type) { + if (!type.isPrimitive()) return type; + if (type == int.class) return Integer.class; + if (type == long.class) return Long.class; + if (type == boolean.class) return Boolean.class; + if (type == double.class) return Double.class; + if (type == float.class) return Float.class; + if (type == char.class) return Character.class; + if (type == byte.class) return Byte.class; + if (type == short.class) return Short.class; + return type; } /** From 2b609b4b570cf6891fb88869ee01ca5c1d9108c8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 11:26:07 +0000 Subject: [PATCH 38/65] fixed deprecated private channel tests --- .../java/org/finos/fdc3/proxy/steps/ChannelSteps.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 index 4e9da592..d36a0a1f 100644 --- 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 @@ -161,15 +161,22 @@ public void pipesTypesTo(String typeHandlerName, String field) { List types = new ArrayList<>(); world.set(field, types); - ContextHandler ch = new ContextHandler() { - + class MyHandler implements ContextHandler, EventHandler { + @Override public void handleContext(Context context, ContextMetadata metadata) { types.add(context.getType()); } + + @Override + public void handleEvent(FDC3Event event) { + types.add(event.getType().toString()); + } }; + MyHandler ch = new MyHandler(); + world.set(typeHandlerName, ch); } From 93f23d1b0173fe258c8b03a70151ec41586f75f1 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 11:31:23 +0000 Subject: [PATCH 39/65] Fixed remaining deprecated private channel tests --- .../test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index d36a0a1f..5b107a63 100644 --- 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 @@ -171,7 +171,9 @@ public void handleContext(Context context, ContextMetadata metadata) { @Override public void handleEvent(FDC3Event event) { - types.add(event.getType().toString()); + @SuppressWarnings("unchecked") + Map details = (Map) event.getDetails(); + types.add((String) details.get("contextType")); } }; From fdebe9c46cbfe59f7ac427f4f165424399fec137 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 12:07:11 +0000 Subject: [PATCH 40/65] Fixing more tests --- .../org/finos/fdc3/proxy/TestSpringConfig.java | 8 +++++--- .../responses/RaiseIntentForContextResponse.java | 15 +++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) 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 index 6a03c299..b656cd4f 100644 --- 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 @@ -16,13 +16,15 @@ package org.finos.fdc3.proxy; -import io.cucumber.spring.ScenarioScope; import org.finos.fdc3.proxy.world.CustomWorld; import org.finos.fdc3.testing.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.Primary; +import org.springframework.context.annotation.ScopedProxyMode; + +import io.cucumber.spring.ScenarioScope; /** * Spring configuration for Cucumber tests. @@ -42,7 +44,7 @@ public class TestSpringConfig { * Create CustomWorld as a scenario-scoped bean. */ @Bean - @ScenarioScope + @ScenarioScope(proxyMode = ScopedProxyMode.NO) public CustomWorld customWorld() { return new CustomWorld(); } @@ -54,7 +56,7 @@ public CustomWorld customWorld() { */ @Bean @Primary - @ScenarioScope + @ScenarioScope(proxyMode = ScopedProxyMode.NO) public PropsWorld propsWorld(CustomWorld customWorld) { return customWorld; } 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 index ac1767f1..3bfc4d05 100644 --- 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 @@ -9,6 +9,8 @@ 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; @@ -76,9 +78,10 @@ private List findMatchingIntents(TestMessaging messaging, String c for (IntentDetail detail : messaging.getIntentDetails()) { boolean matches = true; - // Match context type + // 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())) { - // Context type matching is optional + matches = false; } // Match target app if specified @@ -121,7 +124,7 @@ private Map createRaiseIntentForContextResponse(Map uniqueIntents = new HashSet<>(); + Set uniqueIntents = new LinkedHashSet<>(); for (IntentDetail detail : relevant) { if (detail.getIntent() != null) { uniqueIntents.add(detail.getIntent()); @@ -130,21 +133,21 @@ private Map createRaiseIntentForContextResponse(Map> appIntents = new ArrayList<>(); for (String intentName : uniqueIntents) { - Map intentInfo = new HashMap<>(); + 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 HashMap<>(); + 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<>(); + Map appIntent = new LinkedHashMap<>(); appIntent.put("intent", intentInfo); appIntent.put("apps", apps); appIntents.add(appIntent); From be48f7a7ff7b57fc2280f783b6d1af21cdf173d1 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 17:18:30 +0000 Subject: [PATCH 41/65] Adding deprecated methods, fixed channel changed issue --- .../finos/fdc3/proxy/DesktopAgentProxy.java | 14 +++++ .../heartbeat/DefaultHeartbeatSupport.java | 3 +- .../listeners/DesktopAgentEventListener.java | 53 +++++++++++++++++-- .../proxy/steps/ChannelSelectorSteps.java | 5 ++ .../java/org/finos/fdc3/api/DesktopAgent.java | 9 ++++ 5 files changed, 79 insertions(+), 5 deletions(-) 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 index 1050708d..55328c4b 100644 --- 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 @@ -93,11 +93,21 @@ public CompletionStage broadcast(Context context) { 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) { @@ -118,6 +128,10 @@ public CompletionStage leaveCurrentChannel() { public CompletionStage joinUserChannel(String channelId) { return channels.joinUserChannel(channelId); } + + public CompletionStage joinChannel(String channelId) { + return channels.joinUserChannel(channelId); + } @Override public CompletionStage> getCurrentChannel() { 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 index af69ad54..8b5fc2ff 100644 --- 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 @@ -132,7 +132,8 @@ public CompletionStage disconnect() { if (heartbeatListener != null) { messaging.unregister(heartbeatListener.getId()); } + scheduler.shutdown(); - return CompletableFuture.completedFuture(null); + return messaging.disconnect(); } } 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 index c1803cac..33dd05b5 100644 --- 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 @@ -89,17 +89,44 @@ public boolean filter(Map message) { return getExpectedMessageType().equals(type); } + /** + * Maps FDC3 event types to their corresponding message types. + * e.g., "userChannelChanged" -> "channelChangedEvent" + */ private String getExpectedMessageType() { - return eventType + "Event"; + 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 fdc3EventType = toFDC3EventType(eventType); - FDC3Event event = new FDC3Event(fdc3EventType, payload); - handler.handleEvent(event); + + // Restructure the event based on message type + if ("channelChangedEvent".equals(messageType)) { + // Restructure channelChangedEvent to userChannelChanged format + Map details = new HashMap<>(); + String newChannelId = (String) payload.get("newChannelId"); + details.put("currentChannelId", newChannelId); + + FDC3Event event = new FDC3Event(FDC3Event.Type.USER_CHANNEL_CHANGED, details); + handler.handleEvent(event); + } else { + // For other event types (currently unused but meeting the spec) + // Convert message type to FDC3Event.Type + FDC3Event.Type fdc3EventType = messageTypeToFDC3EventType(messageType); + FDC3Event event = new FDC3Event(fdc3EventType, payload); + handler.handleEvent(event); + } } private FDC3EventType toFDC3SchemaEventType(String eventType) { @@ -125,4 +152,22 @@ private FDC3Event.Type toFDC3EventType(String eventType) { throw new RuntimeException("UnknownEventType"); } } + + /** + * Converts a message type (e.g., "channelChangedEvent") to the corresponding + * FDC3Event.Type enum value (e.g., USER_CHANNEL_CHANGED). + * This is used for messages received from the desktop agent. + */ + private FDC3Event.Type messageTypeToFDC3EventType(String messageType) { + if (messageType == null) { + throw new RuntimeException("UnknownEventType"); + } + switch (messageType) { + case "channelChangedEvent": + return FDC3Event.Type.USER_CHANNEL_CHANGED; + default: + // Currently unused but provided for future event types + throw new RuntimeException("UnknownEventType"); + } + } } 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 index 1d94f056..08f97bfd 100644 --- 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 @@ -21,6 +21,7 @@ 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.proxy.Connectable; import org.finos.fdc3.proxy.DesktopAgentProxy; @@ -73,6 +74,10 @@ public void aChannelSelectorAndDesktopAgent(String selectorField, String daField 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}") diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index fd2fa16b..0e10d8e9 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -769,4 +769,13 @@ default CompletionStage raiseIntentForContext(Context context) * ``` */ CompletionStage getAppMetadata(AppIdentifier app); + + @Deprecated + public CompletionStage> getSystemChannels(); + + @Deprecated + public CompletionStage joinChannel(String channelId); + + @Deprecated + public CompletionStage addContextListener(ContextHandler ch); } From ee2fe05cd73e9b0fe69e42d267c6200f5c091a41 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 17:41:51 +0000 Subject: [PATCH 42/65] Fixed issue around non-existent channels --- .../proxy/channels/DefaultChannelSupport.java | 20 +++++++++++++------ .../proxy/listeners/RegisterableListener.java | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) 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 index 20c036f1..0833d25b 100644 --- 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 @@ -90,8 +90,10 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto String newChannelId = details != null ? (String) details.get("currentChannelId") : null; Logger.debug("Desktop Agent reports channel changed: {}", newChannelId); - getUserChannelsCached().thenAccept(channels -> { + getUserChannelsCached().thenCompose(channels -> { Channel theChannel = null; + + // If there's a newChannelId, retrieve details of the channel if (newChannelId != null) { theChannel = channels.stream() .filter(c -> newChannelId.equals(c.getId())) @@ -99,23 +101,29 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto .orElse(null); if (theChannel == null) { - Logger.debug("Unknown user channel, querying Desktop Agent: {}", newChannelId); - getUserChannels().thenAccept(updatedChannels -> { + // Channel not found - query user channels in case they have changed + Logger.debug("Unknown user channel, querying Desktop Agent for updated user channels: {}", newChannelId); + return getUserChannels().thenApply(updatedChannels -> { Channel foundChannel = updatedChannels.stream() .filter(c -> newChannelId.equals(c.getId())) .findFirst() .orElse(null); + if (foundChannel == null) { - Logger.warn("Received user channel update with unknown user channel: {}", newChannelId); + Logger.warn("Received user channel update with unknown user channel (user channel listeners will not work): {}", newChannelId); } + currentChannel = foundChannel; - channelSelector.updateChannel(newChannelId, updatedChannels); + channelSelector.updateChannel(foundChannel != null ? foundChannel.getId() : null, updatedChannels); + return null; }); - return; } } + + // Channel found in cache or newChannelId is null currentChannel = theChannel; channelSelector.updateChannel(theChannel != null ? theChannel.getId() : null, channels); + return CompletableFuture.completedFuture(null); }); }, "userChannelChanged"); } 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 index 0b4e4680..59cc0d3f 100644 --- 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 @@ -18,13 +18,13 @@ import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Predicate; + +import org.finos.fdc3.api.types.Listener; /** * A listener that can be registered with the messaging system. */ -public interface RegisterableListener { +public interface RegisterableListener extends Listener { /** * Get the unique identifier for this listener. From 895ff1cb4f4972d30c3cd488a3058b6ae19dbea6 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 22 Dec 2025 18:02:25 +0000 Subject: [PATCH 43/65] All tests passing --- .../proxy/channels/DefaultPrivateChannel.java | 15 ++++++--------- .../fdc3/proxy/messaging/AbstractMessaging.java | 6 +++--- .../finos/fdc3/api/channel/PrivateChannel.java | 6 +++--- 3 files changed, 12 insertions(+), 15 deletions(-) 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 index 1a96c143..0082d3d7 100644 --- 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 @@ -71,7 +71,7 @@ public CompletionStage addEventListener(String type, EventHandler hand } @Override - public Listener onAddContextListener(EventHandler handler) { + public CompletionStage onAddContextListener(EventHandler handler) { // Adapt handler type for differences between addEventListener and onAddContextListener handler types PrivateChannelAddContextEventListener listener = new PrivateChannelAddContextEventListener( messaging, messageExchangeTimeout, getId(), @@ -82,12 +82,11 @@ messaging, messageExchangeTimeout, getId(), handler.handleEvent(new FDC3Event(FDC3Event.Type.ADD_CONTEXT_LISTENER, details)); }); // Register asynchronously (fire and forget) like TypeScript - listener.register(); - return listener; + return listener.register().thenApply(v -> listener); } @Override - public Listener onUnsubscribe(EventHandler handler) { + public CompletionStage onUnsubscribe(EventHandler handler) { // Adapt handler type for differences between addEventListener and onUnsubscribe handler types PrivateChannelUnsubscribeEventListener listener = new PrivateChannelUnsubscribeEventListener( messaging, messageExchangeTimeout, getId(), @@ -98,18 +97,16 @@ messaging, messageExchangeTimeout, getId(), handler.handleEvent(new FDC3Event(FDC3Event.Type.ON_UNSUBSCRIBE, details)); }); // Register asynchronously (fire and forget) like TypeScript - listener.register(); - return listener; + return listener.register().thenApply(v -> listener); } @Override - public Listener onDisconnect(EventHandler handler) { + 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))); - listener.register(); - return listener; + return listener.register().thenApply(v -> listener); } @Override 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 index f80a0000..6ff50f36 100644 --- 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 @@ -159,9 +159,9 @@ public CompletionStage exchange(Map message, String expec ); Logger.debug("Sending to DesktopAgent: {}", message); - post(message); - - return promise.thenApply(response -> { + + // 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) { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java index 161df162..da3ebc14 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/PrivateChannel.java @@ -63,7 +63,7 @@ public interface PrivateChannel extends Channel { * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ @Deprecated - Listener onAddContextListener(EventHandler handler); + CompletionStage onAddContextListener(EventHandler handler); /** * Adds a listener that will be called whenever the remote app invokes Listener.unsubscribe() on a context listener that it @@ -73,7 +73,7 @@ public interface PrivateChannel extends Channel { * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ @Deprecated - Listener onUnsubscribe(EventHandler handler); + CompletionStage onUnsubscribe(EventHandler handler); /** * Adds a listener that will be called when the remote app terminates, for example when its window is closed or because @@ -82,7 +82,7 @@ public interface PrivateChannel extends Channel { * @deprecated Use {@link #addEventListener(String, EventHandler)} instead */ @Deprecated - Listener onDisconnect(EventHandler handler); + CompletionStage onDisconnect(EventHandler handler); /** * May be called to indicate that a participant will no longer interact with this channel. After this function has been called, From 81446d4d9c345d07140bfc74b825dcf4e6448ec0 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 9 Jan 2026 10:26:15 +0000 Subject: [PATCH 44/65] Added GetAgent --- .../finos/fdc3/proxy/DesktopAgentProxy.java | 8 +- .../proxy/channels/DefaultChannelSupport.java | 49 ++- .../proxy/heartbeat/HeartbeatSupport.java | 2 +- .../proxy/intents/DefaultIntentSupport.java | 1 + .../finos/fdc3/proxy/steps/AgentSteps.java | 2 +- .../proxy/steps/ChannelSelectorSteps.java | 2 +- .../proxy/support/SimpleChannelSelector.java | 14 +- .../proxy/support/SimpleIntentResolver.java | 14 +- fdc3-get-agent/pom.xml | 88 ++++ .../org/finos/fdc3/getagent/GetAgent.java | 416 ++++++++++++++++++ .../finos/fdc3/getagent/GetAgentParams.java | 307 +++++++++++++ .../fdc3/getagent/WebSocketMessaging.java | 212 +++++++++ .../getagent/ui/DefaultChannelSelector.java | 60 +++ .../getagent/ui/DefaultIntentResolver.java | 150 +++++++ .../finos/fdc3/api/ui}/ChannelSelector.java | 22 +- .../org/finos/fdc3/api/ui}/Connectable.java | 6 +- .../fdc3/api/ui}/IntentResolutionChoice.java | 22 +- .../finos/fdc3/api/ui}/IntentResolver.java | 21 +- pom.xml | 1 + 19 files changed, 1347 insertions(+), 50 deletions(-) create mode 100644 fdc3-get-agent/pom.xml create mode 100644 fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java create mode 100644 fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgentParams.java create mode 100644 fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/WebSocketMessaging.java create mode 100644 fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultChannelSelector.java create mode 100644 fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultIntentResolver.java rename {fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels => fdc3-standard/src/main/java/org/finos/fdc3/api/ui}/ChannelSelector.java (53%) rename {fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy => fdc3-standard/src/main/java/org/finos/fdc3/api/ui}/Connectable.java (86%) rename {fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents => fdc3-standard/src/main/java/org/finos/fdc3/api/ui}/IntentResolutionChoice.java (66%) rename {fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents => fdc3-standard/src/main/java/org/finos/fdc3/api/ui}/IntentResolver.java (57%) 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 index 55328c4b..82c76a4f 100644 --- 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 @@ -35,6 +35,7 @@ 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; @@ -93,7 +94,7 @@ public CompletionStage broadcast(Context context) { public CompletionStage addContextListener(String contextType, ContextHandler handler) { return channels.addContextListener(handler, contextType); } - + @Override public CompletionStage addContextListener(ContextHandler handler) { return channels.addContextListener(handler, null); @@ -103,7 +104,7 @@ public CompletionStage addContextListener(ContextHandler handler) { public CompletionStage> getUserChannels() { return channels.getUserChannels(); } - + @Deprecated public CompletionStage> getSystemChannels() { return channels.getUserChannels(); @@ -128,7 +129,7 @@ public CompletionStage leaveCurrentChannel() { public CompletionStage joinUserChannel(String channelId) { return channels.joinUserChannel(channelId); } - + public CompletionStage joinChannel(String channelId) { return channels.joinUserChannel(channelId); } @@ -221,4 +222,3 @@ public AppSupport getApps() { return apps; } } - 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 index 0833d25b..21c68973 100644 --- 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 @@ -30,6 +30,7 @@ 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.proxy.Messaging; import org.finos.fdc3.proxy.listeners.DesktopAgentEventListener; import org.finos.fdc3.proxy.util.Logger; @@ -92,7 +93,7 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto getUserChannelsCached().thenCompose(channels -> { Channel theChannel = null; - + // If there's a newChannelId, retrieve details of the channel if (newChannelId != null) { theChannel = channels.stream() @@ -102,24 +103,28 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto if (theChannel == null) { // Channel not found - query user channels in case they have changed - Logger.debug("Unknown user channel, querying Desktop Agent for updated user channels: {}", newChannelId); + Logger.debug("Unknown user channel, querying Desktop Agent for updated user channels: {}", + newChannelId); return getUserChannels().thenApply(updatedChannels -> { Channel foundChannel = updatedChannels.stream() .filter(c -> newChannelId.equals(c.getId())) .findFirst() .orElse(null); - + if (foundChannel == null) { - Logger.warn("Received user channel update with unknown user channel (user channel listeners will not work): {}", newChannelId); + Logger.warn( + "Received user channel update with unknown user channel (user channel listeners will not work): {}", + newChannelId); } - + currentChannel = foundChannel; - channelSelector.updateChannel(foundChannel != null ? foundChannel.getId() : null, updatedChannels); + channelSelector.updateChannel(foundChannel != null ? foundChannel.getId() : null, + updatedChannels); return null; }); } } - + // Channel found in cache or newChannelId is null currentChannel = theChannel; channelSelector.updateChannel(theChannel != null ? theChannel.getId() : null, channels); @@ -150,13 +155,13 @@ public CompletionStage getUserChannel() { .convertValue(response, GetCurrentChannelResponse.class); if (typedResponse.getPayload() == null || - typedResponse.getPayload().getChannel() == 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, + return new DefaultChannel(messaging, messageExchangeTimeout, schemaChannel.getID(), Channel.Type.User, schemaChannel.getDisplayMetadata()); }); } @@ -183,7 +188,7 @@ public CompletionStage> getUserChannels() { .convertValue(response, GetUserChannelsResponse.class); if (typedResponse.getPayload() == null || - typedResponse.getPayload().getUserChannels() == null) { + typedResponse.getPayload().getUserChannels() == null) { userChannels = new ArrayList<>(); return userChannels; } @@ -191,7 +196,8 @@ public CompletionStage> getUserChannels() { // 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())) + messaging, messageExchangeTimeout, c.getID(), Channel.Type.User, + c.getDisplayMetadata())) .collect(Collectors.toList()); return userChannels; @@ -216,13 +222,14 @@ public CompletionStage getOrCreate(String id) { .convertValue(response, GetOrCreateChannelResponse.class); if (typedResponse.getPayload() == null || - typedResponse.getPayload().getChannel() == 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()); + return new DefaultChannel(messaging, messageExchangeTimeout, id, Channel.Type.App, + schemaChannel.getDisplayMetadata()); }); } @@ -235,13 +242,14 @@ public CompletionStage createPrivateChannel() { Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(requestMap, "createPrivateChannelResponse", messageExchangeTimeout) + return messaging + .>exchange(requestMap, "createPrivateChannelResponse", messageExchangeTimeout) .thenApply(response -> { CreatePrivateChannelResponse typedResponse = messaging.getConverter() .convertValue(response, CreatePrivateChannelResponse.class); if (typedResponse.getPayload() == null || - typedResponse.getPayload().getPrivateChannel() == null) { + typedResponse.getPayload().getPrivateChannel() == null) { throw new RuntimeException(ChannelError.CreationFailed.toString()); } @@ -259,7 +267,8 @@ public CompletionStage leaveUserChannel() { Map requestMap = messaging.getConverter().toMap(request); - return messaging.>exchange(requestMap, "leaveCurrentChannelResponse", messageExchangeTimeout) + return messaging + .>exchange(requestMap, "leaveCurrentChannelResponse", messageExchangeTimeout) .thenCompose(response -> { currentChannel = null; return getUserChannelsCached().thenAccept(channels -> { @@ -293,7 +302,7 @@ public CompletionStage joinUserChannel(String id) { } channelSelector.updateChannel(id, channels); - + // Notify all user channel listeners of the channel change for (UserChannelContextListener listener : userChannelListeners) { listener.changeChannel(); @@ -303,7 +312,8 @@ public CompletionStage joinUserChannel(String id) { @Override public CompletionStage addContextListener(ContextHandler handler, String type) { - DefaultUserChannelContextListener listener = new DefaultUserChannelContextListener(this, messaging, messageExchangeTimeout, type, handler); + DefaultUserChannelContextListener listener = new DefaultUserChannelContextListener(this, messaging, + messageExchangeTimeout, type, handler); userChannelListeners.add(listener); return listener.register().thenApply(v -> listener); } @@ -313,7 +323,8 @@ Channel getCurrentChannelInternal() { return currentChannel; } - // Package-private for UserChannelContextListener to remove itself on unsubscribe + // 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/heartbeat/HeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java index 83406473..22fdb0ba 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java @@ -16,7 +16,7 @@ package org.finos.fdc3.proxy.heartbeat; -import org.finos.fdc3.proxy.Connectable; +import org.finos.fdc3.api.ui.Connectable; /** * Interface for heartbeat support. 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 index aa16c959..e27a85bb 100644 --- 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 @@ -29,6 +29,7 @@ import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.IntentHandler; 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.listeners.DefaultIntentListener; import org.finos.fdc3.schema.*; 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 index 8304a8b7..fa41fe3c 100644 --- 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 @@ -22,7 +22,7 @@ import java.util.Map; import org.finos.fdc3.api.context.Context; -import org.finos.fdc3.proxy.Connectable; +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; 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 index 08f97bfd..505370cb 100644 --- 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 @@ -23,7 +23,7 @@ import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.context.Context; -import org.finos.fdc3.proxy.Connectable; +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; 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 index 2c54968e..dbbaf20c 100644 --- 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 @@ -18,10 +18,12 @@ 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.proxy.channels.ChannelSelector; +import org.finos.fdc3.api.ui.ChannelSelector; import org.finos.fdc3.testing.world.PropsWorld; /** @@ -94,5 +96,15 @@ public String getChannelId() { 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 index 429015e4..6815df44 100644 --- 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 @@ -26,8 +26,8 @@ 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.proxy.intents.IntentResolutionChoice; -import org.finos.fdc3.proxy.intents.IntentResolver; +import org.finos.fdc3.api.ui.IntentResolutionChoice; +import org.finos.fdc3.api.ui.IntentResolver; import org.finos.fdc3.testing.world.PropsWorld; /** @@ -78,5 +78,15 @@ public CompletionStage chooseIntent(List appI return CompletableFuture.completedFuture(resolution); } + + @Override + public CompletionStage connect() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); + } } diff --git a/fdc3-get-agent/pom.xml b/fdc3-get-agent/pom.xml new file mode 100644 index 00000000..607ce0e7 --- /dev/null +++ b/fdc3-get-agent/pom.xml @@ -0,0 +1,88 @@ + + + + 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 + + + + + + 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.slf4j + slf4j-api + 2.0.9 + + + + + 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} + + + + + 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..62825d1f --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java @@ -0,0 +1,416 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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.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.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; + +/** + * Factory for obtaining a DesktopAgent connection via WebSocket. + *

+ * This class handles the Web Connection Protocol (WCP) handshake to establish + * a connection to an FDC3 Desktop Agent over WebSocket. + *

+ * Usage example: + *

{@code
+ * GetAgentParams params = GetAgentParams.builder()
+ *     .webSocketUrl("ws://localhost:8080/fdc3")
+ *     .identityUrl("https://myapp.example.com/")
+ *     .channelSelector(myChannelSelector)
+ *     .intentResolver(myIntentResolver)
+ *     .build();
+ *
+ * DesktopAgent agent = GetAgent.getAgent(params).toCompletableFuture().get();
+ * }
+ */ +public class GetAgent { + + private static final String WCP4_VALIDATE_APP_IDENTITY = "WCP4ValidateAppIdentity"; + private static final String WCP5_VALIDATE_APP_IDENTITY_RESPONSE = "WCP5ValidateAppIdentityResponse"; + private static final String WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE = "WCP5ValidateAppIdentityFailedResponse"; + + private GetAgent() { + // Utility class - no instantiation + } + + /** + * Obtains a DesktopAgent connection using the provided parameters. + *

+ * This method: + *

    + *
  1. Establishes a WebSocket connection to the Desktop Agent
  2. + *
  3. Sends a WCP4ValidateAppIdentity message
  4. + *
  5. Waits for WCP5ValidateAppIdentityResponse or WCP5ValidateAppIdentityFailedResponse
  6. + *
  7. If successful, constructs and returns a DesktopAgentProxy
  8. + *
+ * + * @param params the connection parameters + * @return a CompletionStage that completes with the DesktopAgent, or fails with an exception + * @throws FDC3ConnectionException if the connection fails + */ + public static CompletionStage getAgent(GetAgentParams params) { + Logger.info("Initiating Desktop Agent connection to {}", params.getWebSocketUrl()); + + // Create a temporary AppIdentifier for the initial connection + // This will be updated once we receive the identity validation response + 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 -> { + // Clean up on failure + 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); + }); + } + + /** + * Performs the WCP4/WCP5 handshake with the Desktop Agent. + */ + private static CompletionStage performHandshake( + WebSocketMessaging messaging, GetAgentParams params) { + + String connectionAttemptUuid = UUID.randomUUID().toString(); + + // Build WCP4ValidateAppIdentity message + Map validateMessage = new HashMap<>(); + validateMessage.put("type", WCP4_VALIDATE_APP_IDENTITY); + + Map meta = new HashMap<>(); + meta.put("connectionAttemptUuid", connectionAttemptUuid); + meta.put("timestamp", OffsetDateTime.now().toString()); + validateMessage.put("meta", meta); + + Map payload = new HashMap<>(); + payload.put("identityUrl", params.getIdentityUrl()); + payload.put("actualUrl", params.getIdentityUrl()); // In Java, these are typically the same + + // Include instanceId and instanceUuid if provided (for reconnection) + if (params.getInstanceId() != null) { + payload.put("instanceId", params.getInstanceId()); + } + if (params.getInstanceUuid() != null) { + payload.put("instanceUuid", params.getInstanceUuid()); + } + validateMessage.put("payload", payload); + + // Set up response listener + 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 (!WCP5_VALIDATE_APP_IDENTITY_RESPONSE.equals(type) && + !WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE.equals(type)) { + return false; + } + + // Verify the connectionAttemptUuid matches + 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 (WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE.equals(type)) { + String errorMessage = responsePayload != null + ? (String) responsePayload.get("message") + : "Identity validation failed"; + responseFuture.completeExceptionally( + new FDC3ConnectionException("Identity validation failed: " + errorMessage)); + return; + } + + // Parse successful response + try { + ValidationResult result = new ValidationResult(); + result.appId = (String) responsePayload.get("appId"); + result.instanceId = (String) responsePayload.get("instanceId"); + result.instanceUuid = (String) responsePayload.get("instanceUuid"); + + // Parse implementation metadata + Map implMeta = (Map) responsePayload.get("implementationMetadata"); + if (implMeta != null) { + result.implementationMetadata = parseImplementationMetadata(implMeta); + } + + Logger.info("Identity validation successful - appId: {}, instanceId: {}", + result.appId, result.instanceId); + responseFuture.complete(result); + } catch (Exception e) { + responseFuture.completeExceptionally( + new FDC3ConnectionException("Failed to parse validation response", e)); + } + } + + @Override + public CompletionStage register() { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletionStage unsubscribe() { + messaging.unregister(id); + return CompletableFuture.completedFuture(null); + } + }); + + // Send the validation message + Logger.debug("Sending WCP4ValidateAppIdentity message"); + messaging.post(validateMessage); + + // Apply timeout + 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 identity validation"); + } + if (error instanceof FDC3ConnectionException) { + throw (FDC3ConnectionException) error; + } + throw new FDC3ConnectionException("Handshake failed", error); + }); + } + + /** + * Parses the implementation metadata from the validation response. + */ + @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")); + + // Parse app metadata + 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); + } + + // Parse optional features + 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; + } + + /** + * Creates the DesktopAgentProxy with all support components. + */ + private static DesktopAgent createDesktopAgent( + WebSocketMessaging messaging, + ValidationResult validationResult, + GetAgentParams params) { + + // Create the final AppIdentifier with the validated identity + AppIdentifier appIdentifier = new AppIdentifier( + validationResult.appId, + validationResult.instanceId, + null // desktopAgent is not set on the app's own identifier + ); + + // Create a Messaging wrapper with the correct AppIdentifier + org.finos.fdc3.proxy.Messaging finalMessaging = new WebSocketMessagingWrapper(messaging, appIdentifier); + + // Create support components + DefaultHeartbeatSupport heartbeatSupport = new DefaultHeartbeatSupport( + finalMessaging, params.getHeartbeatIntervalMs()); + + DefaultChannelSupport channelSupport = new DefaultChannelSupport( + finalMessaging, params.getChannelSelector(), params.getMessageExchangeTimeout()); + + DefaultIntentSupport intentSupport = new DefaultIntentSupport( + finalMessaging, params.getIntentResolver(), + params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); + + DefaultAppSupport appSupport = new DefaultAppSupport( + finalMessaging, params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); + + // Build list of connectables (for connect/disconnect lifecycle) + List connectables = new ArrayList<>(); + connectables.add(heartbeatSupport); + + // Create and return the DesktopAgentProxy + DesktopAgentProxy proxy = new DesktopAgentProxy( + heartbeatSupport, + channelSupport, + intentSupport, + appSupport, + connectables); + + // Start the heartbeat and other connectables + proxy.connect(); + + Logger.info("DesktopAgent proxy created successfully"); + return proxy; + } + + /** + * Holds the result of a successful identity validation. + */ + private static class ValidationResult { + String appId; + String instanceId; + String instanceUuid; + ImplementationMetadata implementationMetadata; + } + + /** + * Wrapper around WebSocketMessaging to provide the correct AppIdentifier + * after identity validation while delegating all other operations. + */ + private static class WebSocketMessagingWrapper implements org.finos.fdc3.proxy.Messaging { + private final WebSocketMessaging delegate; + private final AppIdentifier appIdentifier; + + WebSocketMessagingWrapper(WebSocketMessaging delegate, AppIdentifier appIdentifier) { + this.delegate = delegate; + this.appIdentifier = appIdentifier; + } + + @Override + public String createUUID() { + return delegate.createUUID(); + } + + @Override + public CompletionStage post(Map message) { + return delegate.post(message); + } + + @Override + public void register(org.finos.fdc3.proxy.listeners.RegisterableListener listener) { + delegate.register(listener); + } + + @Override + public void unregister(String id) { + delegate.unregister(id); + } + + @Override + public org.finos.fdc3.schema.AddContextListenerRequestMeta createMeta() { + // Create meta with the correct AppIdentifier + org.finos.fdc3.schema.AddContextListenerRequestMeta meta = + new org.finos.fdc3.schema.AddContextListenerRequestMeta(); + meta.setRequestUUID(createUUID()); + meta.setTimestamp(OffsetDateTime.now()); + meta.setSource(appIdentifier); + return meta; + } + + @Override + public CompletionStage waitFor( + java.util.function.Predicate filter, long timeoutMs, String timeoutErrorMessage) { + return delegate.waitFor(filter, timeoutMs, timeoutErrorMessage); + } + + @Override + public CompletionStage exchange( + Map message, String expectedTypeName, long timeoutMs) { + return delegate.exchange(message, expectedTypeName, timeoutMs); + } + + @Override + public AppIdentifier getAppIdentifier() { + return appIdentifier; + } + + @Override + public CompletionStage disconnect() { + return delegate.disconnect(); + } + + @Override + public org.finos.fdc3.schema.SchemaConverter getConverter() { + return delegate.getConverter(); + } + } +} 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..8a748e68 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgentParams.java @@ -0,0 +1,307 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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. + *

+ * This class contains all the configuration needed to establish a connection + * to an FDC3 Desktop Agent over WebSocket using the Web Connection Protocol (WCP). + */ +public class GetAgentParams { + + private final String webSocketUrl; + private final String identityUrl; + 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.identityUrl = builder.identityUrl; + 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; + } + + /** + * Gets the WebSocket URL to connect to the Desktop Agent. + * + * @return the WebSocket URL + */ + public String getWebSocketUrl() { + return webSocketUrl; + } + + /** + * Gets the identity URL used to identify this application. + * This URL is sent to the Desktop Agent during the handshake. + * + * @return the identity URL + */ + public String getIdentityUrl() { + return identityUrl; + } + + /** + * Gets the instance ID used to identify this application instance. + * This is sent to the Desktop Agent during the handshake. + * + * @return the instance ID + */ + public String getInstanceId() { + return instanceId; + } + + /** + * Gets the instance UUID used as a shared secret with the Desktop Agent. + * This is used to validate the application's identity during reconnection. + * + * @return the instance UUID + */ + public String getInstanceUuid() { + return instanceUuid; + } + + /** + * Gets the channel selector implementation for user channel selection UI. + * + * @return the channel selector + */ + public ChannelSelector getChannelSelector() { + return channelSelector; + } + + /** + * Gets the intent resolver implementation for intent resolution UI. + * + * @return the intent resolver + */ + public IntentResolver getIntentResolver() { + return intentResolver; + } + + /** + * Gets the connection timeout in milliseconds. + * + * @return the timeout in milliseconds + */ + public long getTimeoutMs() { + return timeoutMs; + } + + /** + * Gets the message exchange timeout in milliseconds. + * This is used for API calls to the Desktop Agent. + * + * @return the message exchange timeout + */ + public long getMessageExchangeTimeout() { + return messageExchangeTimeout; + } + + /** + * Gets the app launch timeout in milliseconds. + * This is used when opening apps or raising intents. + * + * @return the app launch timeout + */ + public long getAppLaunchTimeout() { + return appLaunchTimeout; + } + + /** + * Gets the heartbeat interval in milliseconds. + * + * @return the heartbeat interval + */ + public long getHeartbeatIntervalMs() { + return heartbeatIntervalMs; + } + + /** + * Creates a new builder for GetAgentParams. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for GetAgentParams. + */ + public static class Builder { + private String webSocketUrl; + private String identityUrl; + private String instanceId; + private String instanceUuid; + 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; + + /** + * Sets the WebSocket URL to connect to the Desktop Agent. + * + * @param webSocketUrl the WebSocket URL (required) + * @return this builder + */ + public Builder webSocketUrl(String webSocketUrl) { + this.webSocketUrl = webSocketUrl; + return this; + } + + /** + * Sets the identity URL used to identify this application. + * + * @param identityUrl the identity URL (required) + * @return this builder + */ + public Builder identityUrl(String identityUrl) { + this.identityUrl = identityUrl; + return this; + } + + /** + * Sets the instance ID used to identify this application instance. + * + * @param instanceId the instance ID (required) + * @return this builder + */ + public Builder instanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + /** + * Sets the instance UUID used as a shared secret with the Desktop Agent. + * + * @param instanceUuid the instance UUID (required) + * @return this builder + */ + public Builder instanceUuid(String instanceUuid) { + this.instanceUuid = instanceUuid; + return this; + } + + /** + * Sets the channel selector implementation. + * + * @param channelSelector the channel selector (default: NullChannelSelector) + * @return this builder + */ + public Builder channelSelector(ChannelSelector channelSelector) { + this.channelSelector = channelSelector != null ? channelSelector : new DefaultChannelSelector(); + return this; + } + + /** + * Sets the intent resolver implementation. + * + * @param intentResolver the intent resolver (default: NullIntentResolver) + * @return this builder + */ + public Builder intentResolver(IntentResolver intentResolver) { + this.intentResolver = intentResolver != null ? intentResolver : new DefaultIntentResolver(); + return this; + } + + /** + * Sets the connection timeout in milliseconds. + * + * @param timeoutMs the timeout (default: 10000) + * @return this builder + */ + public Builder timeoutMs(long timeoutMs) { + this.timeoutMs = timeoutMs; + return this; + } + + /** + * Sets the message exchange timeout in milliseconds. + * + * @param messageExchangeTimeout the timeout (default: 10000) + * @return this builder + */ + public Builder messageExchangeTimeout(long messageExchangeTimeout) { + this.messageExchangeTimeout = messageExchangeTimeout; + return this; + } + + /** + * Sets the app launch timeout in milliseconds. + * + * @param appLaunchTimeout the timeout (default: 30000) + * @return this builder + */ + public Builder appLaunchTimeout(long appLaunchTimeout) { + this.appLaunchTimeout = appLaunchTimeout; + return this; + } + + /** + * Sets the heartbeat interval in milliseconds. + * + * @param heartbeatIntervalMs the interval (default: 5000) + * @return this builder + */ + public Builder heartbeatIntervalMs(long heartbeatIntervalMs) { + this.heartbeatIntervalMs = heartbeatIntervalMs; + return this; + } + + /** + * Builds the GetAgentParams instance. + * + * @return the built GetAgentParams + * @throws IllegalArgumentException if required parameters are missing + */ + public GetAgentParams build() { + if (webSocketUrl == null || webSocketUrl.isEmpty()) { + throw new IllegalArgumentException("webSocketUrl is required"); + } + if (identityUrl == null || identityUrl.isEmpty()) { + throw new IllegalArgumentException("identityUrl is required"); + } + if (instanceId == null || instanceId.isEmpty()) { + throw new IllegalArgumentException("instanceId is required"); + } + if (instanceUuid == null || instanceUuid.isEmpty()) { + throw new IllegalArgumentException("instanceUuid is required"); + } + 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..9e05ad3a --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/WebSocketMessaging.java @@ -0,0 +1,212 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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 WCP6Goodbye message before closing + Map goodbye = new HashMap<>(); + goodbye.put("type", "WCP6Goodbye"); + Map meta = new HashMap<>(); + meta.put("timestamp", OffsetDateTime.now()); + goodbye.put("meta", meta); + + return post(goodbye).whenComplete((v, error) -> { + // Close the WebSocket regardless of whether the goodbye was sent successfully + try { + session.close(); + } catch (IOException e) { + Logger.error("Error closing WebSocket: {}", e.getMessage()); + } + + // Clear all listeners + listeners.clear(); + connected = false; + }); + } + + /** + * 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..2ee3dc28 --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultChannelSelector.java @@ -0,0 +1,60 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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..3f04ee3b --- /dev/null +++ b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/ui/DefaultIntentResolver.java @@ -0,0 +1,150 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/ChannelSelector.java similarity index 53% rename from fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/ui/ChannelSelector.java index 7df428cf..fbfe715b 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/channels/ChannelSelector.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/ChannelSelector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.finos.fdc3.proxy.channels; +package org.finos.fdc3.api.ui; import java.util.List; import java.util.function.Consumer; @@ -22,23 +22,27 @@ import org.finos.fdc3.api.channel.Channel; /** - * Interface for channel selection UI components. + * Interface used by the desktop agent proxy to handle the channel selection process. + *

+ * Implementations of this interface provide a UI for users to select which user channel + * they want to join. The Desktop Agent proxy will call {@link #updateChannel} when + * the channel list or current channel changes, and the implementation should call + * the callback set via {@link #setChannelChangeCallback} when the user selects a channel. */ -public interface ChannelSelector { +public interface ChannelSelector extends Connectable { /** * Set the callback to be invoked when the user selects a different channel. * - * @param callback the callback, receiving the channel ID or null if leaving + * @param callback the callback, receiving the channel ID or null if leaving all channels */ void setChannelChangeCallback(Consumer callback); /** - * Update the current channel and available channels. + * Called when the list of user channels is updated, or the selected channel changes. * - * @param channelId the current channel ID, or null if none - * @param channels the available channels + * @param channelId the current channel ID, or null if no channel is selected + * @param availableChannels the list of available user channels */ - void updateChannel(String channelId, List channels); + void updateChannel(String channelId, List availableChannels); } - diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/Connectable.java similarity index 86% rename from fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/ui/Connectable.java index 4afb0729..dfd7541d 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/Connectable.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/Connectable.java @@ -14,12 +14,15 @@ * limitations under the License. */ -package org.finos.fdc3.proxy; +package org.finos.fdc3.api.ui; import java.util.concurrent.CompletionStage; /** * Interface for objects that support connection lifecycle. + *

+ * This is used by UI components like {@link ChannelSelector} and {@link IntentResolver} + * that may need to connect to external resources. */ public interface Connectable { @@ -37,4 +40,3 @@ public interface Connectable { */ CompletionStage disconnect(); } - diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolutionChoice.java similarity index 66% rename from fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolutionChoice.java index b9f1c263..2bdecb63 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolutionChoice.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolutionChoice.java @@ -14,29 +14,47 @@ * limitations under the License. */ -package org.finos.fdc3.proxy.intents; +package org.finos.fdc3.api.ui; import org.finos.fdc3.api.types.AppIdentifier; /** * Represents the user's choice from an intent resolver. + *

+ * This is returned by {@link IntentResolver#chooseIntent} when the user + * selects an application to handle an intent. */ public class IntentResolutionChoice { private final String intent; private final AppIdentifier appId; + /** + * Creates a new IntentResolutionChoice. + * + * @param intent the intent that was chosen + * @param appId the application identifier chosen to handle the intent + */ public IntentResolutionChoice(String intent, AppIdentifier appId) { this.intent = intent; this.appId = appId; } + /** + * Gets the intent that was chosen. + * + * @return the intent name + */ public String getIntent() { return intent; } + /** + * Gets the application identifier chosen to handle the intent. + * + * @return the app identifier + */ public AppIdentifier getAppId() { return appId; } } - diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolver.java similarity index 57% rename from fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java rename to fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolver.java index 6f754f83..44b516b4 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/intents/IntentResolver.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/ui/IntentResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.finos.fdc3.proxy.intents; +package org.finos.fdc3.api.ui; import java.util.List; import java.util.concurrent.CompletionStage; @@ -23,17 +23,22 @@ import org.finos.fdc3.api.metadata.AppIntent; /** - * Interface for intent resolution UI components. + * Interface used by the desktop agent proxy to handle the intent resolution process. + *

+ * Implementations of this interface provide a UI for users to choose which application + * should handle an intent when multiple applications are available. */ -public interface IntentResolver { +public interface IntentResolver extends Connectable { /** - * Display a UI to let the user choose an intent and app. + * Called when the user needs to resolve an intent. + *

+ * The implementation should display a UI allowing the user to select from the + * available applications that can handle the intent(s). * - * @param appIntents the available intents and apps - * @param context the context being passed - * @return a CompletionStage containing the user's choice, or null if cancelled + * @param appIntents the available intents and apps that can handle them + * @param context the context being passed to the intent + * @return a CompletionStage containing the user's choice, or null if the operation was cancelled */ CompletionStage chooseIntent(List appIntents, Context context); } - diff --git a/pom.xml b/pom.xml index c137e4cf..a4220909 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ fdc3-context fdc3-testing fdc3-agent-proxy + fdc3-get-agent 11 From 216a5f20c908e75a4631b9f696c9f6d8967aed44 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 9 Jan 2026 13:15:21 +0000 Subject: [PATCH 45/65] Added getAgent tests --- fdc3-agent-proxy/pom.xml | 57 +--- fdc3-get-agent/pom.xml | 23 +- .../getagent/CucumberSpringConfiguration.java | 29 ++ .../finos/fdc3/getagent/TestSpringConfig.java | 48 ++++ .../fdc3/getagent/steps/GetAgentSteps.java | 0 .../getagent/support/MockWebSocketServer.java | 271 ++++++++++++++++++ .../test/resources/features/get-agent.feature | 52 ++++ fdc3-testing/pom.xml | 29 ++ 8 files changed, 451 insertions(+), 58 deletions(-) create mode 100644 fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java create mode 100644 fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java create mode 100644 fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/steps/GetAgentSteps.java create mode 100644 fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/support/MockWebSocketServer.java create mode 100644 fdc3-get-agent/src/test/resources/features/get-agent.feature diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index cb04bfbf..b34de535 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -18,9 +18,8 @@ UTF-8 11 11 - 7.15.0 - 5.10.1 - + ${project.basedir}/../../FDC3/packages/fdc3-agent-proxy/test/features @@ -54,57 +53,7 @@ 2.0.9 - - - io.cucumber - cucumber-java - ${cucumber.version} - test - - - io.cucumber - cucumber-spring - ${cucumber.version} - test - - - org.springframework - spring-context - 6.1.2 - test - - - org.springframework - spring-test - 6.1.2 - test - - - io.cucumber - cucumber-junit-platform-engine - ${cucumber.version} - test - - - org.junit.platform - junit-platform-suite - 1.10.1 - test - - - org.junit.jupiter - junit-jupiter - ${junit.version} - test - - - - - com.jayway.jsonpath - json-path - 2.9.0 - test - + diff --git a/fdc3-get-agent/pom.xml b/fdc3-get-agent/pom.xml index 607ce0e7..c2b720f3 100644 --- a/fdc3-get-agent/pom.xml +++ b/fdc3-get-agent/pom.xml @@ -63,11 +63,11 @@ 2.0.9 - + - org.junit.jupiter - junit-jupiter - 5.10.1 + org.finos.fdc3 + fdc3-testing + ${project.version} test @@ -83,6 +83,21 @@ ${jdk.target.version} + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + + + + cucumber.junit-platform.naming-strategy=long + cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html + cucumber.glue=org.finos.fdc3.getagent,org.finos.fdc3.getagent.steps,org.finos.fdc3.testing.steps + cucumber.features=classpath:features + + + + diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java new file mode 100644 index 00000000..b41e1149 --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java @@ -0,0 +1,29 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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.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/TestSpringConfig.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java new file mode 100644 index 00000000..b573e68b --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java @@ -0,0 +1,48 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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.testing.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 both local step definitions and the generic steps from fdc3-testing. + */ +@Configuration +@ComponentScan(basePackages = { + "org.finos.fdc3.getagent.steps", + "org.finos.fdc3.testing.steps" +}) +public class TestSpringConfig { + + /** + * Create PropsWorld as a scenario-scoped bean. + * This is shared across all step definition classes within a scenario. + */ + @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..e69de29b 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..0db1760b --- /dev/null +++ b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/support/MockWebSocketServer.java @@ -0,0 +1,271 @@ +/** + * Copyright 2023 Wellington Management Company LLP + * + * 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.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.WebConnectionProtocol1HelloMeta; +import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentity; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponse; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponseType; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponse; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload; +import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponseType; +import org.finos.fdc3.schema.WebConnectionProtocol6Goodbye; +import org.glassfish.tyrus.server.Server; + +/** + * Mock WebSocket server for testing GetAgent. + * Simulates a Desktop Agent responding to WCP messages. + * + * Uses schema types from fdc3-schema for type-safe JSON handling: + * - WebConnectionProtocol4ValidateAppIdentity + * - WebConnectionProtocol5ValidateAppIdentitySuccessResponse + * - WebConnectionProtocol5ValidateAppIdentityFailedResponse + * - WebConnectionProtocol6Goodbye + */ +@ServerEndpoint("/fdc3") +public class MockWebSocketServer { + + private static final SchemaConverter converter = new SchemaConverter(); + + // Instance-specific state (accessed via static holder for @ServerEndpoint) + private static final CopyOnWriteArrayList sessions = new CopyOnWriteArrayList<>(); + private static volatile MockWebSocketServer currentInstance; + + private Server server; + private int port; + + // Configuration for responses + private String acceptedIdentityUrl; + 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"; + + // Received messages for assertions (using typed schema classes) + private WebConnectionProtocol4ValidateAppIdentity lastWCP4; + private WebConnectionProtocol6Goodbye lastWCP6; + + 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 acceptIdentity(String identityUrl, String appId, String instanceId) { + acceptedIdentityUrl = identityUrl; + responseAppId = appId; + responseInstanceId = instanceId; + rejectMessage = null; + shouldTimeout = false; + } + + public void rejectIdentity(String message) { + acceptedIdentityUrl = null; + rejectMessage = message; + shouldTimeout = false; + } + + public void timeoutIdentity() { + shouldTimeout = true; + } + + public void setImplementationMetadata(String provider, String version) { + providerName = provider; + fdc3Version = version; + } + + // Property accessors for assertions + public WebConnectionProtocol4ValidateAppIdentity getLastWCP4() { + return lastWCP4; + } + + public WebConnectionProtocol6Goodbye getLastWCP6() { + return lastWCP6; + } + + // WebSocket endpoint methods - these are called on the endpoint instance + // created by the container, so we delegate to the currentInstance + + @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 { + // First, peek at the type to determine which class to use + String type = extractType(message); + + // Delegate to current instance for state management + MockWebSocketServer instance = currentInstance; + if (instance == null) { + return; + } + + if ("WCP4ValidateAppIdentity".equals(type)) { + WebConnectionProtocol4ValidateAppIdentity wcp4 = + converter.fromJson(message, WebConnectionProtocol4ValidateAppIdentity.class); + instance.lastWCP4 = wcp4; + instance.handleValidateAppIdentity(wcp4, session); + } else if ("WCP6Goodbye".equals(type)) { + WebConnectionProtocol6Goodbye wcp6 = + converter.fromJson(message, WebConnectionProtocol6Goodbye.class); + instance.lastWCP6 = wcp6; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Extract the "type" field from a JSON message without full parsing. + */ + private String extractType(String json) throws IOException { + return converter.getObjectMapper().readTree(json).path("type").asText(null); + } + + private void handleValidateAppIdentity(WebConnectionProtocol4ValidateAppIdentity request, Session session) { + if (shouldTimeout) { + // Don't respond - simulate timeout + return; + } + + String connectionAttemptUuid = request.getMeta().getConnectionAttemptUUID(); + String identityUrl = request.getPayload().getIdentityURL(); + + try { + String responseJson; + + if (rejectMessage != null) { + responseJson = buildFailedResponse(connectionAttemptUuid, rejectMessage); + } else if (acceptedIdentityUrl != null && acceptedIdentityUrl.equals(identityUrl)) { + responseJson = buildSuccessResponse(connectionAttemptUuid); + } else { + responseJson = buildFailedResponse(connectionAttemptUuid, "Unknown identity URL: " + identityUrl); + } + + session.getBasicRemote().sendText(responseJson); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String buildSuccessResponse(String connectionAttemptUuid) throws IOException { + WebConnectionProtocol5ValidateAppIdentitySuccessResponse response = + new WebConnectionProtocol5ValidateAppIdentitySuccessResponse(); + + // Build meta + WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta(); + meta.setConnectionAttemptUUID(connectionAttemptUuid); + meta.setTimestamp(OffsetDateTime.now()); + response.setMeta(meta); + + // Set type + response.setType(WebConnectionProtocol5ValidateAppIdentitySuccessResponseType.WCP5_VALIDATE_APP_IDENTITY_RESPONSE); + + // Build payload + WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload payload = + new WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload(); + payload.setAppID(responseAppId); + payload.setInstanceID(responseInstanceId); + payload.setInstanceUUID(responseInstanceUuid); + + // Build implementation metadata + ImplementationMetadata implMeta = new ImplementationMetadata(); + implMeta.setFdc3Version(fdc3Version); + implMeta.setProvider(providerName); + implMeta.setProviderVersion("1.0.0"); + + // Build app metadata + AppMetadata appMeta = new AppMetadata(); + appMeta.setAppId(responseAppId); + appMeta.setInstanceId(responseInstanceId); + implMeta.setAppMetadata(appMeta); + + // Build optional features + ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); + optFeatures.setOriginatingAppMetadata(true); + optFeatures.setUserChannelMembershipAPIs(true); + optFeatures.setDesktopAgentBridging(false); + implMeta.setOptionalFeatures(optFeatures); + + payload.setImplementationMetadata(implMeta); + response.setPayload(payload); + + return converter.toJson(response); + } + + private String buildFailedResponse(String connectionAttemptUuid, String message) throws IOException { + WebConnectionProtocol5ValidateAppIdentityFailedResponse response = + new WebConnectionProtocol5ValidateAppIdentityFailedResponse(); + + // Build meta + WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta(); + meta.setConnectionAttemptUUID(connectionAttemptUuid); + meta.setTimestamp(OffsetDateTime.now()); + response.setMeta(meta); + + // Set type + response.setType(WebConnectionProtocol5ValidateAppIdentityFailedResponseType.WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE); + + // Build payload + WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload payload = + new WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload(); + 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..dc154fdb --- /dev/null +++ b/fdc3-get-agent/src/test/resources/features/get-agent.feature @@ -0,0 +1,52 @@ +Feature: GetAgent WebSocket Connection + + Background: Test Setup + Given a mock WebSocket server in "server" + + Scenario: Successful connection with valid identity + Given "server" will accept identity for "https://myapp.example.com/" as appId "test-app" instanceId "test-instance" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" + When I call "GetAgent" with "getAgent" with parameter "{params}" + Then the promise "result" should resolve + And "{result}" is not null + When I refer to the server "server" last WCP4 as "wcp4" + Then "{wcp4}" is an object with the following contents + | payload.identityURL | payload.instanceID | payload.instanceUUID | + | https://myapp.example.com/ | test-instance | test-uuid-123 | + + Scenario: Connection fails with invalid identity + Given "server" will reject identity with message "Access denied" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://unknown.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" + When I call "GetAgent" with "getAgent" with parameter "{params}" + Then the promise "result" should resolve + And "{result}" is an error with message "Access denied" + + Scenario: Connection times out + Given "server" will timeout on identity validation + Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" timeout 2000 + When I call "GetAgent" with "getAgent" with parameter "{params}" + Then the promise "result" should resolve + And "{result}" is an error + + Scenario: GetAgent returns implementation metadata + Given "server" will accept identity for "https://myapp.example.com/" 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}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" + When I call "GetAgent" with "getAgent" with parameter "{params}" + Then the promise "result" should resolve + When I call "{result}" with "getInfo" + Then the promise "result" should resolve + And "{result}" is an object with the following contents + | provider | fdc3Version | + | test-provider | 2.0 | + + Scenario: Disconnect sends WCP6Goodbye + Given "server" will accept identity for "https://myapp.example.com/" as appId "test-app" instanceId "test-instance" + Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" + When I call "GetAgent" with "getAgent" with parameter "{params}" + Then the promise "result" should resolve + When I refer to "{result}" as "agent" + When I call "{agent}" with "disconnect" + Then the promise "result" should resolve + When I refer to the server "server" last WCP6 as "wcp6" + Then "{wcp6}" is not null diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml index 9eb7f27d..15f0786c 100644 --- a/fdc3-testing/pom.xml +++ b/fdc3-testing/pom.xml @@ -66,6 +66,23 @@ cucumber-junit-platform-engine ${cucumber.version} + + io.cucumber + cucumber-spring + ${cucumber.version} + + + + + org.springframework + spring-context + 6.1.2 + + + org.springframework + spring-test + 6.1.2 + @@ -99,6 +116,18 @@ slf4j-api 2.0.9 + + + + org.glassfish.tyrus + tyrus-server + 2.1.5 + + + org.glassfish.tyrus + tyrus-container-grizzly-server + 2.1.5 + From 8fdfd34c42f8d125b640a5a4da84d875bc2a3db2 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 9 Jan 2026 13:18:55 +0000 Subject: [PATCH 46/65] Updated readme --- README.md | 158 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 425e8694..2d9b6526 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,150 @@ -![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 +## Overview -Prerequisite: Java 11 +This project provides: -#### FDC3 Java API +- **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 -In a new terminal, navigate to the `fdc3api` directory +## Modules -Build Project +| 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 | +| `fdc3-testing` | Cucumber step definitions and test utilities for FDC3 conformance testing | -```sh -mvn clean compile -``` +## Requirements -#### FDC3 Container +- 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 `fdc3container` directory +## Installation -Install Dependencies +### Building from Source ```sh -npm i +mvn clean install ``` -Run Application +### Maven Dependency -```sh -npm start +Once published, add to your `pom.xml`: + +```xml + + org.finos.fdc3 + fdc3-get-agent + 1.0.0-SNAPSHOT + ``` -Runs on +## Usage -#### Client +### Connecting to a Desktop Agent -In a new terminal, navigate to the `client` directory +```java +import org.finos.fdc3.getagent.GetAgent; +import org.finos.fdc3.getagent.GetAgentParams; +import org.finos.fdc3.api.DesktopAgent; -Build Project: +// Connect to a Desktop Agent via WebSocket +GetAgentParams params = new GetAgentParams.Builder() + .identityUrl("https://myapp.example.com") + .channelSelector(true) + .intentResolver(true) + .build(); -```sh -mvn clean compile package +DesktopAgent agent = GetAgent.getAgent(params); + +// Now use the agent +agent.broadcast(new Contact("john.doe@example.com", "John Doe")); ``` -Run the executable from Target directory +### Broadcasting Context -```sh -java -jar \client\target\client-1.0.0-SNAPSHOT.jar - ``` +```java +// Create and broadcast a context +Context contact = new Contact("jane@example.com", "Jane Smith"); +agent.broadcast(contact); +``` -#### Stock Search React Receiver Application +### Listening for Context -In a new terminal, navigate to the `fdcreceiver-react-trade-app` directory +```java +// Add a context listener +agent.addContextListener("fdc3.contact", context -> { + System.out.println("Received contact: " + context); +}); +``` -Install Dependencies +### Raising Intents -```sh -npm i +```java +// Raise an intent +IntentResolution resolution = agent.raiseIntent("ViewChart", instrument); ``` -Run Application +## Architecture -```sh -npm start +``` +┌─────────────────────────────────────────────────────────────┐ +│ Java Application │ +├─────────────────────────────────────────────────────────────┤ +│ GetAgent → DesktopAgentProxy → WebSocketMessaging │ +└──────────────────────────┬──────────────────────────────────┘ + │ WebSocket (DACP) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Desktop Agent │ +│ (e.g., FDC3 Sail, Finsemble) │ +└─────────────────────────────────────────────────────────────┘ ``` -Runs on - -## Usage with the Current State of this Repo +The Java API communicates with a Desktop Agent using the FDC3 Desktop Agent Communication Protocol (DACP) over WebSocket, enabling interoperability with any DACP-compliant agent. -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: +## Testing -* 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` +### Running Tests -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. +```sh +mvn test +``` -![Demo Screenshot](readme-images/demo_screenshot.png) +### Conformance Testing -## Usage in a Business Environment +The `fdc3-agent-proxy` module reuses Cucumber feature files from the official [FDC3 TypeScript repository](https://github.com/finos/FDC3) to ensure conformance with the specification. -Some firms have existing Java desktop applications, and they want to use FDC3 to integrate with other apps that use JavaScript or other technologies. +Feature files are copied from: -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. +``` +FDC3/packages/fdc3-agent-proxy/test/features/ +``` -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. +## Project Status -## Roadmap +This project is under active development. Current focus areas: -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. +- [ ] Complete implementation of `DesktopAgentProxy` messaging +- [ ] Full coverage of FDC3 2.2 API +- [ ] Conformance test suite passing +- [ ] Channel selector and intent resolver UI components +- [ ] Published Maven artifacts ## 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,7 +153,7 @@ 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 From 3274c5660210e3e0a8ea6e91b0ea1bb3b1f0bd27 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 9 Jan 2026 13:26:37 +0000 Subject: [PATCH 47/65] Removed copyright in headers --- .gitignore | 3 +- README.md | 75 ++++--------------- .../finos/fdc3/proxy/DesktopAgentProxy.java | 2 +- .../java/org/finos/fdc3/proxy/Messaging.java | 2 +- .../org/finos/fdc3/proxy/apps/AppSupport.java | 2 +- .../fdc3/proxy/apps/DefaultAppSupport.java | 2 +- .../fdc3/proxy/channels/ChannelSupport.java | 2 +- .../fdc3/proxy/channels/DefaultChannel.java | 2 +- .../proxy/channels/DefaultChannelSupport.java | 2 +- .../proxy/channels/DefaultPrivateChannel.java | 2 +- .../DefaultUserChannelContextListener.java | 2 +- .../channels/UserChannelContextListener.java | 2 +- .../heartbeat/DefaultHeartbeatSupport.java | 2 +- .../proxy/heartbeat/HeartbeatSupport.java | 2 +- .../intents/DefaultIntentResolution.java | 2 +- .../proxy/intents/DefaultIntentSupport.java | 2 +- .../fdc3/proxy/intents/IntentSupport.java | 2 +- .../proxy/listeners/AbstractListener.java | 2 +- .../AbstractPrivateChannelEventListener.java | 2 +- .../listeners/DefaultContextListener.java | 2 +- .../listeners/DefaultIntentListener.java | 2 +- .../listeners/DesktopAgentEventListener.java | 2 +- ...PrivateChannelAddContextEventListener.java | 2 +- ...PrivateChannelDisconnectEventListener.java | 2 +- .../PrivateChannelNullEventListener.java | 2 +- ...rivateChannelUnsubscribeEventListener.java | 2 +- .../proxy/listeners/RegisterableListener.java | 2 +- .../proxy/messaging/AbstractMessaging.java | 2 +- .../org/finos/fdc3/proxy/util/Logger.java | 2 +- .../org/finos/fdc3/proxy/CucumberHooks.java | 2 +- .../proxy/CucumberSpringConfiguration.java | 2 +- .../finos/fdc3/proxy/TestSpringConfig.java | 2 +- .../finos/fdc3/proxy/steps/AgentSteps.java | 2 +- .../proxy/steps/ChannelSelectorSteps.java | 2 +- .../finos/fdc3/proxy/steps/ChannelSteps.java | 2 +- .../finos/fdc3/proxy/steps/IntentSteps.java | 2 +- .../org/finos/fdc3/proxy/steps/UtilSteps.java | 2 +- .../finos/fdc3/proxy/support/ContextMap.java | 2 +- .../proxy/support/SimpleChannelSelector.java | 2 +- .../proxy/support/SimpleIntentResolver.java | 2 +- .../fdc3/proxy/support/TestMessaging.java | 2 +- .../responses/AddEventListenerResponse.java | 2 +- .../support/responses/AutomaticResponse.java | 2 +- .../responses/ChannelStateResponse.java | 2 +- .../CreatePrivateChannelResponse.java | 2 +- .../DisconnectPrivateChannelResponse.java | 2 +- .../responses/FindInstancesResponse.java | 2 +- .../FindIntentByContextResponse.java | 2 +- .../support/responses/FindIntentResponse.java | 2 +- .../responses/GetAppMetadataResponse.java | 2 +- .../support/responses/GetInfoResponse.java | 2 +- .../responses/GetOrCreateChannelResponse.java | 2 +- .../responses/GetUserChannelsResponse.java | 2 +- .../responses/IntentResultResponse.java | 2 +- .../proxy/support/responses/OpenResponse.java | 2 +- .../RaiseIntentForContextResponse.java | 2 +- .../responses/RaiseIntentResponse.java | 2 +- .../responses/RegisterListenersResponse.java | 2 +- .../support/responses/ResponseSupport.java | 2 +- .../UnsubscribeListenersResponse.java | 2 +- .../finos/fdc3/proxy/world/CustomWorld.java | 2 +- .../org/finos/fdc3/getagent/GetAgent.java | 2 +- .../finos/fdc3/getagent/GetAgentParams.java | 2 +- .../fdc3/getagent/WebSocketMessaging.java | 2 +- .../getagent/ui/DefaultChannelSelector.java | 2 +- .../getagent/ui/DefaultIntentResolver.java | 2 +- .../getagent/CucumberSpringConfiguration.java | 2 +- .../finos/fdc3/getagent/TestSpringConfig.java | 2 +- .../getagent/support/MockWebSocketServer.java | 2 +- fdc3-standard/pom.xml | 2 +- .../java/org/finos/fdc3/api/DesktopAgent.java | 2 +- .../org/finos/fdc3/api/channel/Channel.java | 2 +- .../fdc3/api/channel/PrivateChannel.java | 2 +- .../org/finos/fdc3/api/context/Context.java | 2 +- .../finos/fdc3/api/errors/ChannelError.java | 2 +- .../api/errors/FDC3ConnectionException.java | 2 +- .../org/finos/fdc3/api/errors/OpenError.java | 2 +- .../finos/fdc3/api/errors/ResolveError.java | 2 +- .../finos/fdc3/api/errors/ResultError.java | 2 +- .../finos/fdc3/api/metadata/AppIntent.java | 2 +- .../finos/fdc3/api/metadata/AppMetadata.java | 2 +- .../fdc3/api/metadata/ContextMetadata.java | 2 +- .../fdc3/api/metadata/DisplayMetadata.java | 2 +- .../org/finos/fdc3/api/metadata/Icon.java | 2 +- .../org/finos/fdc3/api/metadata/Image.java | 2 +- .../api/metadata/ImplementationMetadata.java | 2 +- .../fdc3/api/metadata/IntentMetadata.java | 2 +- .../fdc3/api/metadata/IntentResolution.java | 2 +- .../finos/fdc3/api/types/AppIdentifier.java | 2 +- .../finos/fdc3/api/types/ContextHandler.java | 2 +- .../finos/fdc3/api/types/EventHandler.java | 2 +- .../org/finos/fdc3/api/types/FDC3Event.java | 2 +- .../finos/fdc3/api/types/IntentHandler.java | 2 +- .../finos/fdc3/api/types/IntentResult.java | 2 +- .../org/finos/fdc3/api/types/Listener.java | 2 +- .../finos/fdc3/api/ui/ChannelSelector.java | 2 +- .../org/finos/fdc3/api/ui/Connectable.java | 2 +- .../fdc3/api/ui/IntentResolutionChoice.java | 2 +- .../org/finos/fdc3/api/ui/IntentResolver.java | 2 +- fdc3-testing/pom.xml | 2 +- .../fdc3/testing/steps/GenericSteps.java | 2 +- .../fdc3/testing/support/MatchingUtils.java | 2 +- .../finos/fdc3/testing/world/PropsWorld.java | 2 +- pom.xml | 2 +- 104 files changed, 118 insertions(+), 164 deletions(-) diff --git a/.gitignore b/.gitignore index 209617e3..f94a8b2a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ target/ .classpath .project .settings/* -*/.settings \ No newline at end of file +*/.settings +fdc3-java-api.code-workspace diff --git a/README.md b/README.md index 2d9b6526..7e9e501f 100644 --- a/README.md +++ b/README.md @@ -54,23 +54,23 @@ Once published, add to your `pom.xml`: ### Connecting to a Desktop Agent -```java +````java import org.finos.fdc3.getagent.GetAgent; import org.finos.fdc3.getagent.GetAgentParams; import org.finos.fdc3.api.DesktopAgent; +import java.util.UUID; // Connect to a Desktop Agent via WebSocket -GetAgentParams params = new GetAgentParams.Builder() - .identityUrl("https://myapp.example.com") - .channelSelector(true) - .intentResolver(true) +GetAgentParams params = GetAgentParams.builder() + .webSocketUrl("ws://localhost:4475") // Desktop Agent WebSocket URL (required) + .identityUrl("https://myapp.example.com") // App identity 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(); -DesktopAgent agent = GetAgent.getAgent(params); - -// Now use the agent -agent.broadcast(new Contact("john.doe@example.com", "John Doe")); -``` +DesktopAgent agent = GetAgent.getAgent(params).toCompletableFuture().get(); ### Broadcasting Context @@ -78,7 +78,7 @@ agent.broadcast(new Contact("john.doe@example.com", "John Doe")); // Create and broadcast a context Context contact = new Contact("jane@example.com", "Jane Smith"); agent.broadcast(contact); -``` +```` ### Listening for Context @@ -92,56 +92,11 @@ agent.addContextListener("fdc3.contact", context -> { ### Raising Intents ```java -// Raise an intent -IntentResolution resolution = agent.raiseIntent("ViewChart", instrument); -``` - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Java Application │ -├─────────────────────────────────────────────────────────────┤ -│ GetAgent → DesktopAgentProxy → WebSocketMessaging │ -└──────────────────────────┬──────────────────────────────────┘ - │ WebSocket (DACP) - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Desktop Agent │ -│ (e.g., FDC3 Sail, Finsemble) │ -└─────────────────────────────────────────────────────────────┘ -``` - -The Java API communicates with a Desktop Agent using the FDC3 Desktop Agent Communication Protocol (DACP) over WebSocket, enabling interoperability with any DACP-compliant agent. - -## Testing - -### Running Tests - -```sh -mvn test -``` - -### Conformance Testing - -The `fdc3-agent-proxy` module reuses Cucumber feature files from the official [FDC3 TypeScript repository](https://github.com/finos/FDC3) to ensure conformance with the specification. - -Feature files are copied from: - -``` -FDC3/packages/fdc3-agent-proxy/test/features/ +// Raise an intent (null app lets the resolver choose) +IntentResolution resolution = agent.raiseIntent("ViewChart", instrument, null) + .toCompletableFuture().get(); ``` -## Project Status - -This project is under active development. Current focus areas: - -- [ ] Complete implementation of `DesktopAgentProxy` messaging -- [ ] Full coverage of FDC3 2.2 API -- [ ] Conformance test suite passing -- [ ] Channel selector and intent resolver UI components -- [ ] Published Maven artifacts - ## Contributing 1. Fork the repository () @@ -157,8 +112,6 @@ _Need an ICLA? Unsure if you are covered under an existing CCLA? Email [help@fin ## License -Copyright 2023 Wellington Management Company LLP - Distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). SPDX-License-Identifier: [Apache-2.0](https://spdx.org/licenses/Apache-2.0) 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 index 82c76a4f..e525adf5 100644 --- 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 @@ -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. 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 index dfa450f6..774e2d65 100644 --- 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 @@ -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. 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 index 5ed3b600..b70516d1 100644 --- 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 @@ -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. 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 index a7527add..2d426d88 100644 --- 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 @@ -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. 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 index 6dddcd49..d1ec2890 100644 --- 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 @@ -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. 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 index 90ada513..e83e438a 100644 --- 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 @@ -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. 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 index 21c68973..f34484a3 100644 --- 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 @@ -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. 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 index 0082d3d7..097d22ad 100644 --- 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 @@ -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. 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 index 39409f86..795ae766 100644 --- 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 @@ -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. 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 index 7cc6fda2..a40ad426 100644 --- 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 @@ -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. 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 index 8b5fc2ff..b31f1729 100644 --- 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 @@ -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. diff --git a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.java index 22fdb0ba..26b68c71 100644 --- a/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/heartbeat/HeartbeatSupport.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. 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 index fb570026..ec843957 100644 --- 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 @@ -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. 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 index e27a85bb..2bb5ffa0 100644 --- 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 @@ -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. 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 index ce6e4d3e..d8db01cf 100644 --- 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 @@ -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. 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 index 26217e71..632426ad 100644 --- 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 @@ -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. 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 index 295f63b4..cd56cffd 100644 --- 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 @@ -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. 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 index 10e53c05..1c22f929 100644 --- 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 @@ -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. 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 index 2804225e..49d1342e 100644 --- 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 @@ -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. 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 index 33dd05b5..bb99d824 100644 --- 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 @@ -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. 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 index 1309d6d9..73eb6f36 100644 --- 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 @@ -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. 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 index 508c8c1e..ccb9c85d 100644 --- 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 @@ -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. 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 index 4f8e6d62..e9dc78c4 100644 --- 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 @@ -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. 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 index 838c516b..fa9c671e 100644 --- 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 @@ -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. 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 index 59cc0d3f..2c8f12a4 100644 --- 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 @@ -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. 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 index 6ff50f36..97da1ea1 100644 --- 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 @@ -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. 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 index 578dffdc..724c3df6 100644 --- 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 @@ -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. diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.java index 6ac7aece..a1b05149 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberHooks.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. diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.java index 337ea82b..d59c1158 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/CucumberSpringConfiguration.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. 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 index b656cd4f..18691f11 100644 --- 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 @@ -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. 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 index fa41fe3c..515fd0f1 100644 --- 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 @@ -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. 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 index 505370cb..dfcf3d26 100644 --- 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 @@ -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. 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 index 5b107a63..8f852177 100644 --- 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 @@ -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. 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 index 6385f5d8..6f8dd6ce 100644 --- 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 @@ -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. 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 index 9d5702a7..983a72e5 100644 --- 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 @@ -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. 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 index c49112f0..a7529493 100644 --- 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 @@ -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. 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 index dbbaf20c..0887a731 100644 --- 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 @@ -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. 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 index 6815df44..cb0d7f8a 100644 --- 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 @@ -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. 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 index a965d51f..c80ad6a5 100644 --- 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 @@ -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. 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 index 59e86e71..eb597695 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 770faed2..e865b33c 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 59ba3137..1702d1e7 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index b518a67e..2b122bfe 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 763f45cc..d5bccc8b 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 794d5c9b..d723d85e 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index cfdfb1df..6868d787 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 2cfd73fa..ec14fa20 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 6d390ee6..11151933 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index cd4f7901..77d07a5c 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 0404d1d4..fd48e8e3 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 5fa9d67a..148cfd08 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 3b0830f2..d52bcff8 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 6957401a..e67c999f 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 3bfc4d05..527bbe0c 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index af704354..9ad2e6bd 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index c7f292ee..0cd4f0c2 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index f2656eaf..ac94ced7 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index e393471c..12370640 100644 --- 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 @@ -1,5 +1,5 @@ /** - * Copyright 2023 Wellington Management Company LLP + * Copyright FINOS and its Contributors */ package org.finos.fdc3.proxy.support.responses; 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 index 8fea4791..48577e78 100644 --- 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 @@ -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. 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 index 62825d1f..f5e9e64a 100644 --- 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 @@ -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. 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 index 8a748e68..ce481c0d 100644 --- 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 @@ -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. 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 index 9e05ad3a..b7ed7362 100644 --- 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 @@ -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. 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 index 2ee3dc28..70705907 100644 --- 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 @@ -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. 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 index 3f04ee3b..81706c0a 100644 --- 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 @@ -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. diff --git a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.java index b41e1149..fd321677 100644 --- a/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/CucumberSpringConfiguration.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. 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 index b573e68b..c22fed37 100644 --- 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 @@ -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. 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 index 0db1760b..2c98750e 100644 --- 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 @@ -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. diff --git a/fdc3-standard/pom.xml b/fdc3-standard/pom.xml index 3e603025..494fe5d5 100644 --- a/fdc3-standard/pom.xml +++ b/fdc3-standard/pom.xml @@ -1,7 +1,7 @@ + + 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..e9237649 --- /dev/null +++ b/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java @@ -0,0 +1,455 @@ +/** + * 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.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 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 for the Desktop Agent
  • + *
+ */ +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; + + public ExampleApp() { + super("FDC3 Example App"); + initUI(); + + // Connect to Desktop Agent on startup + SwingUtilities.invokeLater(this::connectToAgent); + } + + private void initUI() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(600, 500); + 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); + 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 - 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); + + mainPanel.add(listenerPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // Handle window close - cleanup + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + cleanup(); + } + }); + } + + private void connectToAgent() { + log("Connecting to Desktop Agent..."); + + try { + GetAgentParams params = GetAgentParams.builder() + .timeoutMs(30000) + .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)); + log("Successfully connected to Desktop Agent"); + + // Load user channels + loadUserChannels(); + }); + } + + private void onConnectionError(Throwable error) { + statusLabel.setText("Connection Failed"); + statusLabel.setForeground(Color.RED); + log("ERROR: Failed to connect - " + error.getMessage()); + + // Show error dialog + JOptionPane.showMessageDialog(this, + "Failed to connect to Desktop Agent:\n" + error.getMessage() + + "\n\nMake sure the following system properties are set:\n" + + "- FDC3_WEBSOCKET_URL\n" + + "- FDC3_INSTANCE_ID\n" + + "- FDC3_INSTANCE_UUID", + "Connection Error", + JOptionPane.ERROR_MESSAGE); + } + + 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"); + } + }); + }) + .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())); + }) + .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"); + }); + }) + .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; + }); + } + + 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/src/main/java/org/finos/fdc3/getagent/GetAgent.java b/fdc3-get-agent/src/main/java/org/finos/fdc3/getagent/GetAgent.java index f5e9e64a..ef2c24b4 100644 --- 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 @@ -18,7 +18,6 @@ import java.time.OffsetDateTime; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -38,6 +37,10 @@ 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.WebConnectionProtocol1HelloMeta; +import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentity; +import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentityPayload; +import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentityType; /** * Factory for obtaining a DesktopAgent connection via WebSocket. @@ -49,7 +52,8 @@ *
{@code
  * GetAgentParams params = GetAgentParams.builder()
  *     .webSocketUrl("ws://localhost:8080/fdc3")
- *     .identityUrl("https://myapp.example.com/")
+ *     .instanceId("my-app-instance-1")
+ *     .instanceUuid("550e8400-e29b-41d4-a716-446655440000")
  *     .channelSelector(myChannelSelector)
  *     .intentResolver(myIntentResolver)
  *     .build();
@@ -59,7 +63,6 @@
  */
 public class GetAgent {
 
-    private static final String WCP4_VALIDATE_APP_IDENTITY = "WCP4ValidateAppIdentity";
     private static final String WCP5_VALIDATE_APP_IDENTITY_RESPONSE = "WCP5ValidateAppIdentityResponse";
     private static final String WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE = "WCP5ValidateAppIdentityFailedResponse";
 
@@ -116,27 +119,27 @@ private static CompletionStage performHandshake(
         
         String connectionAttemptUuid = UUID.randomUUID().toString();
 
-        // Build WCP4ValidateAppIdentity message
-        Map validateMessage = new HashMap<>();
-        validateMessage.put("type", WCP4_VALIDATE_APP_IDENTITY);
+        // Build WCP4ValidateAppIdentity message using schema classes
+        WebConnectionProtocol4ValidateAppIdentity validateMsg = new WebConnectionProtocol4ValidateAppIdentity();
+        validateMsg.setType(WebConnectionProtocol4ValidateAppIdentityType.WCP4_VALIDATE_APP_IDENTITY);
 
-        Map meta = new HashMap<>();
-        meta.put("connectionAttemptUuid", connectionAttemptUuid);
-        meta.put("timestamp", OffsetDateTime.now().toString());
-        validateMessage.put("meta", meta);
+        WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta();
+        meta.setConnectionAttemptUUID(connectionAttemptUuid);
+        meta.setTimestamp(OffsetDateTime.now());
+        validateMsg.setMeta(meta);
 
-        Map payload = new HashMap<>();
-        payload.put("identityUrl", params.getIdentityUrl());
-        payload.put("actualUrl", params.getIdentityUrl()); // In Java, these are typically the same
+        WebConnectionProtocol4ValidateAppIdentityPayload payload = new WebConnectionProtocol4ValidateAppIdentityPayload();
+        // For native apps, use "native" as the identity and actual URLs
+        payload.setIdentityURL("native");
+        payload.setActualURL("native");
 
-        // Include instanceId and instanceUuid if provided (for reconnection)
-        if (params.getInstanceId() != null) {
-            payload.put("instanceId", params.getInstanceId());
-        }
-        if (params.getInstanceUuid() != null) {
-            payload.put("instanceUuid", params.getInstanceUuid());
-        }
-        validateMessage.put("payload", payload);
+        // Include instanceId and instanceUuid (required for native apps)
+        payload.setInstanceID(params.getInstanceId());
+        payload.setInstanceUUID(params.getInstanceUuid());
+        validateMsg.setPayload(payload);
+
+        // Convert to Map for sending
+        Map validateMessage = messaging.getConverter().toMap(validateMsg);
 
         // Set up response listener
         CompletableFuture responseFuture = new CompletableFuture<>();
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
index e69de29b..8a4cb65b 100644
--- 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
@@ -0,0 +1,283 @@
+/**
+ * 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.
+ * 

+ * This class contains all the configuration needed to establish a connection + * to an FDC3 Desktop Agent over WebSocket using the Web Connection Protocol (WCP). + *

+ * The following system properties can be used to provide default values: + *

    + *
  • {@code FDC3_WEBSOCKET_URL} - Default WebSocket URL
  • + *
+ * Values set via the builder will override system property defaults. + */ +public class GetAgentParams { + + /** System property name for the WebSocket URL default. */ + public static final String PROP_WEBSOCKET_URL = "FDC3_WEBSOCKET_URL"; + + private final String webSocketUrl; + 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.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; + } + + /** + * Gets the WebSocket URL to connect to the Desktop Agent. + * + * @return the WebSocket URL + */ + public String getWebSocketUrl() { + return webSocketUrl; + } + + /** + * Gets the instance ID used to identify this application instance. + * This is sent to the Desktop Agent during the handshake. + * + * @return the instance ID + */ + public String getInstanceId() { + return instanceId; + } + + /** + * Gets the instance UUID used as a shared secret with the Desktop Agent. + * This is used to validate the application's identity during reconnection. + * + * @return the instance UUID + */ + public String getInstanceUuid() { + return instanceUuid; + } + + /** + * Gets the channel selector implementation for user channel selection UI. + * + * @return the channel selector + */ + public ChannelSelector getChannelSelector() { + return channelSelector; + } + + /** + * Gets the intent resolver implementation for intent resolution UI. + * + * @return the intent resolver + */ + public IntentResolver getIntentResolver() { + return intentResolver; + } + + /** + * Gets the connection timeout in milliseconds. + * + * @return the timeout in milliseconds + */ + public long getTimeoutMs() { + return timeoutMs; + } + + /** + * Gets the message exchange timeout in milliseconds. + * This is used for API calls to the Desktop Agent. + * + * @return the message exchange timeout + */ + public long getMessageExchangeTimeout() { + return messageExchangeTimeout; + } + + /** + * Gets the app launch timeout in milliseconds. + * This is used when opening apps or raising intents. + * + * @return the app launch timeout + */ + public long getAppLaunchTimeout() { + return appLaunchTimeout; + } + + /** + * Gets the heartbeat interval in milliseconds. + * + * @return the heartbeat interval + */ + public long getHeartbeatIntervalMs() { + return heartbeatIntervalMs; + } + + /** + * Creates a new builder for GetAgentParams. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for GetAgentParams. + */ + public static class Builder { + private String webSocketUrl = System.getProperty(PROP_WEBSOCKET_URL); + 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; + + /** + * Sets the WebSocket URL to connect to the Desktop Agent. + * + * @param webSocketUrl the WebSocket URL (required) + * @return this builder + */ + public Builder webSocketUrl(String webSocketUrl) { + this.webSocketUrl = webSocketUrl; + return this; + } + + /** + * Sets the instance ID used to identify this application instance. + * + * @param instanceId the instance ID (required) + * @return this builder + */ + public Builder instanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + /** + * Sets the instance UUID used as a shared secret with the Desktop Agent. + * + * @param instanceUuid the instance UUID (required) + * @return this builder + */ + public Builder instanceUuid(String instanceUuid) { + this.instanceUuid = instanceUuid; + return this; + } + + /** + * Sets the channel selector implementation. + * + * @param channelSelector the channel selector (default: NullChannelSelector) + * @return this builder + */ + public Builder channelSelector(ChannelSelector channelSelector) { + this.channelSelector = channelSelector != null ? channelSelector : new DefaultChannelSelector(); + return this; + } + + /** + * Sets the intent resolver implementation. + * + * @param intentResolver the intent resolver (default: NullIntentResolver) + * @return this builder + */ + public Builder intentResolver(IntentResolver intentResolver) { + this.intentResolver = intentResolver != null ? intentResolver : new DefaultIntentResolver(); + return this; + } + + /** + * Sets the connection timeout in milliseconds. + * + * @param timeoutMs the timeout (default: 10000) + * @return this builder + */ + public Builder timeoutMs(long timeoutMs) { + this.timeoutMs = timeoutMs; + return this; + } + + /** + * Sets the message exchange timeout in milliseconds. + * + * @param messageExchangeTimeout the timeout (default: 10000) + * @return this builder + */ + public Builder messageExchangeTimeout(long messageExchangeTimeout) { + this.messageExchangeTimeout = messageExchangeTimeout; + return this; + } + + /** + * Sets the app launch timeout in milliseconds. + * + * @param appLaunchTimeout the timeout (default: 30000) + * @return this builder + */ + public Builder appLaunchTimeout(long appLaunchTimeout) { + this.appLaunchTimeout = appLaunchTimeout; + return this; + } + + /** + * Sets the heartbeat interval in milliseconds. + * + * @param heartbeatIntervalMs the interval (default: 5000) + * @return this builder + */ + public Builder heartbeatIntervalMs(long heartbeatIntervalMs) { + this.heartbeatIntervalMs = heartbeatIntervalMs; + return this; + } + + /** + * Builds the GetAgentParams instance. + * + * @return the built GetAgentParams + * @throws IllegalArgumentException if required parameters are missing + */ + public GetAgentParams build() { + if (webSocketUrl == null || webSocketUrl.isEmpty()) { + throw new IllegalArgumentException("webSocketUrl is required"); + } + return new GetAgentParams(this); + } + } +} diff --git a/pom.xml b/pom.xml index 5be6e317..6e4e98cf 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ fdc3-testing fdc3-agent-proxy fdc3-get-agent + fdc3-example-app 11 From e35cff664a386154d45be05b98e527f826d32b4e Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 19 Jan 2026 11:14:05 +0000 Subject: [PATCH 50/65] Added dialog for FDC3 url --- .vscode/launch.json | 6 +- fdc3-example-app/dependency-reduced-pom.xml | 58 +++++ .../org/finos/fdc3/example/ExampleApp.java | 215 ++++++++++++++++-- 3 files changed, 263 insertions(+), 16 deletions(-) create mode 100644 fdc3-example-app/dependency-reduced-pom.xml diff --git a/.vscode/launch.json b/.vscode/launch.json index 3e2b3750..d7f75dd3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,11 @@ "type": "java", "name": "Current File", "request": "launch", - "mainClass": "${file}" + "mainClass": "${file}", + "vmArgs": [ + "-Dfdc3.log.level=DEBUG", + "-DFDC3_WEBSOCKET_URL=http://localhost:8090/remote/user-fce30a8d-95eb-4fb4-b34d-27ce9fdb10df/87664652dcaedaa4" + ] }, { "type": "java", 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/src/main/java/org/finos/fdc3/example/ExampleApp.java b/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java index e9237649..ef7d070f 100644 --- 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 @@ -28,11 +28,14 @@ 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. @@ -66,18 +69,77 @@ public class ExampleApp extends JFrame { private JButton removeListenerButton; private JLabel statusLabel; private JLabel listenerStatusLabel; + + // Broadcast buttons + private JButton broadcastInstrumentButton; + private JButton broadcastCurrencyButton; + private JButton broadcastContactButton; + + // WebSocket URL (set from environment or user prompt) + private String websocketUrl; public ExampleApp() { super("FDC3 Example App"); initUI(); - // Connect to Desktop Agent on startup - SwingUtilities.invokeLater(this::connectToAgent); + // 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() { + // Check environment variable first + websocketUrl = System.getenv("FDC3_WEBSOCKET_URL"); + + // Also check system property as fallback + if (websocketUrl == null || websocketUrl.isEmpty()) { + websocketUrl = System.getProperty("FDC3_WEBSOCKET_URL"); + } + + if (websocketUrl == null || websocketUrl.isEmpty()) { + // Prompt user for the URL + promptForWebSocketUrl(); + } else { + // URL is set, proceed with connection + log("Using FDC3_WEBSOCKET_URL: " + websocketUrl); + connectToAgent(); + } + } + + /** + * Show a dialog prompting the user for the WebSocket URL. + */ + private void promptForWebSocketUrl() { + String message = "FDC3_WEBSOCKET_URL environment variable is not set.\n\n" + + "Please enter the WebSocket URL to connect to the Desktop Agent.\n" + + "You can find this URL in the Sail app directory for native apps.\n\n" + + "Example: ws://localhost:8090/remote/user-abc123/1a2b3c4d5e6f"; + + String url = JOptionPane.showInputDialog( + this, + message, + "Enter WebSocket URL", + JOptionPane.QUESTION_MESSAGE); + + if (url != null && !url.trim().isEmpty()) { + websocketUrl = url.trim(); + log("Using user-provided WebSocket URL: " + websocketUrl); + connectToAgent(); + } else { + // User cancelled or entered empty string + statusLabel.setText("Not Connected"); + statusLabel.setForeground(Color.RED); + log("No WebSocket URL provided. Cannot connect to Desktop Agent."); + log("Set FDC3_WEBSOCKET_URL environment variable or restart and enter URL."); + } } private void initUI() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(600, 500); + setSize(650, 600); setLocationRelativeTo(null); // Main panel with padding @@ -132,7 +194,10 @@ private void initUI() { mainPanel.add(logPanel, BorderLayout.CENTER); - // Bottom panel - Listener controls + // 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")); @@ -149,8 +214,34 @@ private void initUI() { 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(listenerPanel, BorderLayout.SOUTH); + mainPanel.add(bottomPanel, BorderLayout.SOUTH); add(mainPanel); @@ -164,11 +255,17 @@ public void windowClosing(WindowEvent e) { } private void connectToAgent() { - log("Connecting to Desktop Agent..."); + if (websocketUrl == null || websocketUrl.isEmpty()) { + log("ERROR: No WebSocket URL configured"); + return; + } + + log("Connecting to Desktop Agent at: " + websocketUrl); try { GetAgentParams params = GetAgentParams.builder() .timeoutMs(30000) + .webSocketUrl(websocketUrl) .build(); GetAgent.getAgent(params) @@ -199,15 +296,22 @@ private void onConnectionError(Throwable error) { statusLabel.setForeground(Color.RED); log("ERROR: Failed to connect - " + error.getMessage()); - // Show error dialog - JOptionPane.showMessageDialog(this, + // Show error dialog with option to retry + int result = JOptionPane.showOptionDialog(this, "Failed to connect to Desktop Agent:\n" + error.getMessage() + - "\n\nMake sure the following system properties are set:\n" + - "- FDC3_WEBSOCKET_URL\n" + - "- FDC3_INSTANCE_ID\n" + - "- FDC3_INSTANCE_UUID", + "\n\nURL: " + websocketUrl + + "\n\nWould you like to enter a different URL?", "Connection Error", - JOptionPane.ERROR_MESSAGE); + JOptionPane.YES_NO_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + new String[]{"Enter New URL", "Close"}, + "Enter New URL"); + + if (result == 0) { + // User wants to try a different URL + promptForWebSocketUrl(); + } } private void loadUserChannels() { @@ -261,6 +365,7 @@ private void loadCurrentChannel() { channelComboBox.setSelectedIndex(0); log("Not currently joined to any channel"); } + updateBroadcastButtonsState(); }); }) .exceptionally(error -> { @@ -295,8 +400,10 @@ private void joinChannel(Channel channel) { agent.joinUserChannel(channel.getId()) .thenRun(() -> { currentChannel = channel; - SwingUtilities.invokeLater(() -> - log("Joined channel: " + channel.getId())); + SwingUtilities.invokeLater(() -> { + log("Joined channel: " + channel.getId()); + updateBroadcastButtonsState(); + }); }) .exceptionally(error -> { SwingUtilities.invokeLater(() -> { @@ -317,6 +424,7 @@ private void leaveChannel() { SwingUtilities.invokeLater(() -> { channelComboBox.setSelectedIndex(0); log("Left channel"); + updateBroadcastButtonsState(); }); }) .exceptionally(error -> { @@ -370,6 +478,83 @@ private void removeContextListener() { }); } + /** + * 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(); From 917b2842a8c0277106b1fdd7b2039db86f89bdc8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Tue, 20 Jan 2026 14:47:40 +0000 Subject: [PATCH 51/65] Added reconnection logic --- TODO.md | 4 +- .../java/org/finos/fdc3/proxy/Messaging.java | 8 + .../fdc3/proxy/apps/DefaultAppSupport.java | 9 +- .../proxy/messaging/AbstractMessaging.java | 21 ++- .../org/finos/fdc3/example/ExampleApp.java | 146 ++++++++++++++++++ .../org/finos/fdc3/getagent/GetAgent.java | 84 +--------- .../finos/fdc3/api/metadata/AppMetadata.java | 16 ++ 7 files changed, 205 insertions(+), 83 deletions(-) diff --git a/TODO.md b/TODO.md index 3eb669d3..184e95a8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,3 @@ ## TODO -- Documentation in the main FDC3 repo. -- Channel Metadata (intent results) -- Heartbeat interval +- Download Proxy feature files from npm (once they are published there) 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 index 774e2d65..37785c62 100644 --- 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 @@ -110,5 +110,13 @@ public interface Messaging { * @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/DefaultAppSupport.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/apps/DefaultAppSupport.java index 2d426d88..9c5134a2 100644 --- 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 @@ -165,7 +165,14 @@ public CompletionStage getImplementationMetadata() { if (typedResponse.getPayload() != null && typedResponse.getPayload().getImplementationMetadata() != null) { // Schema now uses fdc3-standard ImplementationMetadata directly - return typedResponse.getPayload().getImplementationMetadata(); + 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(); 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 index 97da1ea1..4e9837f9 100644 --- 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 @@ -41,7 +41,8 @@ public abstract class AbstractMessaging implements Messaging { private static final String API_TIMEOUT = "ApiTimeout"; private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - private final AppIdentifier appIdentifier; + private AppIdentifier appIdentifier; + private String instanceUuid; private final SchemaConverter converter; protected AbstractMessaging(AppIdentifier appIdentifier) { @@ -49,6 +50,24 @@ protected AbstractMessaging(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(); 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 index ef7d070f..e4f08f62 100644 --- 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 @@ -77,6 +77,14 @@ public class ExampleApp extends JFrame { // WebSocket URL (set from environment or user prompt) private String websocketUrl; + + // 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"); @@ -155,6 +163,18 @@ private void initUI() { 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 @@ -284,16 +304,54 @@ private void onAgentConnected(DesktopAgent 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 @@ -313,6 +371,94 @@ private void onConnectionError(Throwable error) { promptForWebSocketUrl(); } } + + /** + * 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 || websocketUrl.isEmpty()) { + log("ERROR: No WebSocket URL configured 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) + .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; 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 index ef2c24b4..ce921f52 100644 --- 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 @@ -299,22 +299,22 @@ private static DesktopAgent createDesktopAgent( null // desktopAgent is not set on the app's own identifier ); - // Create a Messaging wrapper with the correct AppIdentifier - org.finos.fdc3.proxy.Messaging finalMessaging = new WebSocketMessagingWrapper(messaging, appIdentifier); + // Update the messaging with the validated identity + messaging.setIdentifier(appIdentifier, validationResult.instanceUuid); // Create support components DefaultHeartbeatSupport heartbeatSupport = new DefaultHeartbeatSupport( - finalMessaging, params.getHeartbeatIntervalMs()); + messaging, params.getHeartbeatIntervalMs()); DefaultChannelSupport channelSupport = new DefaultChannelSupport( - finalMessaging, params.getChannelSelector(), params.getMessageExchangeTimeout()); + messaging, params.getChannelSelector(), params.getMessageExchangeTimeout()); DefaultIntentSupport intentSupport = new DefaultIntentSupport( - finalMessaging, params.getIntentResolver(), + messaging, params.getIntentResolver(), params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); DefaultAppSupport appSupport = new DefaultAppSupport( - finalMessaging, params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); + messaging, params.getMessageExchangeTimeout(), params.getAppLaunchTimeout()); // Build list of connectables (for connect/disconnect lifecycle) List connectables = new ArrayList<>(); @@ -344,76 +344,4 @@ private static class ValidationResult { String instanceUuid; ImplementationMetadata implementationMetadata; } - - /** - * Wrapper around WebSocketMessaging to provide the correct AppIdentifier - * after identity validation while delegating all other operations. - */ - private static class WebSocketMessagingWrapper implements org.finos.fdc3.proxy.Messaging { - private final WebSocketMessaging delegate; - private final AppIdentifier appIdentifier; - - WebSocketMessagingWrapper(WebSocketMessaging delegate, AppIdentifier appIdentifier) { - this.delegate = delegate; - this.appIdentifier = appIdentifier; - } - - @Override - public String createUUID() { - return delegate.createUUID(); - } - - @Override - public CompletionStage post(Map message) { - return delegate.post(message); - } - - @Override - public void register(org.finos.fdc3.proxy.listeners.RegisterableListener listener) { - delegate.register(listener); - } - - @Override - public void unregister(String id) { - delegate.unregister(id); - } - - @Override - public org.finos.fdc3.schema.AddContextListenerRequestMeta createMeta() { - // Create meta with the correct AppIdentifier - org.finos.fdc3.schema.AddContextListenerRequestMeta meta = - new org.finos.fdc3.schema.AddContextListenerRequestMeta(); - meta.setRequestUUID(createUUID()); - meta.setTimestamp(OffsetDateTime.now()); - meta.setSource(appIdentifier); - return meta; - } - - @Override - public CompletionStage waitFor( - java.util.function.Predicate filter, long timeoutMs, String timeoutErrorMessage) { - return delegate.waitFor(filter, timeoutMs, timeoutErrorMessage); - } - - @Override - public CompletionStage exchange( - Map message, String expectedTypeName, long timeoutMs) { - return delegate.exchange(message, expectedTypeName, timeoutMs); - } - - @Override - public AppIdentifier getAppIdentifier() { - return appIdentifier; - } - - @Override - public CompletionStage disconnect() { - return delegate.disconnect(); - } - - @Override - public org.finos.fdc3.schema.SchemaConverter getConverter() { - return delegate.getConverter(); - } - } } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java index b4daf382..42bdf820 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppMetadata.java @@ -46,6 +46,13 @@ public class AppMetadata extends AppIdentifier { private Collection icons; private Collection screenshots; private String resultType; + + /** + * The instance UUID (shared secret) for this connection. + * Used for reconnection to prove the app's identity. + * This is a local extension, not part of the standard FDC3 API. + */ + private String instanceUuid; /** * Default constructor for Jackson deserialization. @@ -154,4 +161,13 @@ public String getResultType() { public void setResultType(String resultType) { this.resultType = resultType; } + + @JsonProperty("instanceUuid") + public String getInstanceUuid() { + return instanceUuid; + } + + public void setInstanceUuid(String instanceUuid) { + this.instanceUuid = instanceUuid; + } } From 108b8b7bf7f865eb6d2790cb2d49c5974041addb Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 16 Mar 2026 15:48:20 +0000 Subject: [PATCH 52/65] Added memaid in readme --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 14a335e6..d7d4a9ff 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,47 @@ 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). +```mermaid +flowchart TB + subgraph JavaApp["Java App"] + direction TB + A1[Build GetAgentParams: webSocketUrl
+ instanceId/instanceUuid for reconnect] + A2[Open WebSocket to Sail] + A3[Send WCP4ValidateAppIdentity
identityURL: native] + A4[Receive WCP5ValidateAppIdentityResponse] + A5[Store appId, instanceId, instanceUuid
from getInfo for reconnection] + A6[DesktopAgentProxy ready] + A7[DACP: broadcastRequest, addContextListenerRequest, etc.] + A8[DACP: broadcastEvent, intentEvent, heartbeatEvent] + end + + subgraph Sail["FDC3 Sail (Desktop Agent)"] + direction TB + S1[Accept WebSocket connection] + S2[Receive WCP4ValidateAppIdentity] + S3[Match native identity to App Directory] + S4[Assign appId, generate instanceId & instanceUuid] + S5[Send WCP5ValidateAppIdentityResponse
appId, instanceId, instanceUuid] + S6[Route DACP requests & events] + end + + A1 --> A2 + A2 --> S1 + S1 --> A3 + A3 --> S2 + S2 --> S3 + S3 --> S4 + S4 --> S5 + S5 --> A4 + A4 --> A5 + A5 --> A6 + A6 --> A7 + A7 <--> S6 + S6 --> A8 +``` + +**App Identity flow:** For a new connection, the Java app sends `WCP4ValidateAppIdentity` with `identityURL: "native"`. FDC3 Sail looks up the app in its App Directory, assigns an `appId`, and generates `instanceId` and `instanceUuid` in the `WCP5ValidateAppIdentityResponse`. The app stores these (via `getInfo()`) for reconnection. On reconnect, the app includes the stored `instanceId` and `instanceUuid` in `WCP4`; Sail validates and returns the same `appId`. After handshake, all FDC3 API calls flow as DACP messages (requests, responses, events) over the WebSocket. + ## Overview This project provides: From f371d2dd05dd25e68d87add71a193567d3e3b3e8 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 16 Mar 2026 15:58:04 +0000 Subject: [PATCH 53/65] Updated diagram --- README.md | 55 +++++++++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d7d4a9ff..0215913e 100644 --- a/README.md +++ b/README.md @@ -5,45 +5,28 @@ 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). ```mermaid -flowchart TB - subgraph JavaApp["Java App"] - direction TB - A1[Build GetAgentParams: webSocketUrl
+ instanceId/instanceUuid for reconnect] - A2[Open WebSocket to Sail] - A3[Send WCP4ValidateAppIdentity
identityURL: native] - A4[Receive WCP5ValidateAppIdentityResponse] - A5[Store appId, instanceId, instanceUuid
from getInfo for reconnection] - A6[DesktopAgentProxy ready] - A7[DACP: broadcastRequest, addContextListenerRequest, etc.] - A8[DACP: broadcastEvent, intentEvent, heartbeatEvent] +sequenceDiagram + participant Sail as FDC3 Sail + participant App as Java App + + 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 + + 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 + + loop DACP message exchange + App->>Sail: broadcastRequest, addContextListenerRequest, etc. + Sail->>App: broadcastEvent, intentEvent, heartbeatEvent, etc. end - - subgraph Sail["FDC3 Sail (Desktop Agent)"] - direction TB - S1[Accept WebSocket connection] - S2[Receive WCP4ValidateAppIdentity] - S3[Match native identity to App Directory] - S4[Assign appId, generate instanceId & instanceUuid] - S5[Send WCP5ValidateAppIdentityResponse
appId, instanceId, instanceUuid] - S6[Route DACP requests & events] - end - - A1 --> A2 - A2 --> S1 - S1 --> A3 - A3 --> S2 - S2 --> S3 - S3 --> S4 - S4 --> S5 - S5 --> A4 - A4 --> A5 - A5 --> A6 - A6 --> A7 - A7 <--> S6 - S6 --> A8 ``` -**App Identity flow:** For a new connection, the Java app sends `WCP4ValidateAppIdentity` with `identityURL: "native"`. FDC3 Sail looks up the app in its App Directory, assigns an `appId`, and generates `instanceId` and `instanceUuid` in the `WCP5ValidateAppIdentityResponse`. The app stores these (via `getInfo()`) for reconnection. On reconnect, the app includes the stored `instanceId` and `instanceUuid` in `WCP4`; Sail validates and returns the same `appId`. After handshake, all FDC3 API calls flow as DACP messages (requests, responses, events) over the WebSocket. +**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. ## Overview From 59b74c4af4dc5a53dde9770ca29b1ad7aed7cdcc Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Thu, 21 May 2026 09:42:01 +0100 Subject: [PATCH 54/65] Updating to FDC3 2.2.3 and standard cucumber steps --- README.md | 1 - fdc3-agent-proxy/pom.xml | 10 +- .../finos/fdc3/proxy/TestSpringConfig.java | 2 +- .../proxy/support/SimpleChannelSelector.java | 3 +- .../proxy/support/SimpleIntentResolver.java | 2 +- .../finos/fdc3/proxy/world/CustomWorld.java | 3 +- .../resources/temporary-features/README.md | 1 + .../temporary-features/app-channels.feature | 129 ++++ .../temporary-features/app-metadata.feature | 35 ++ .../temporary-features/broadcast.feature | 70 +++ .../temporary-features/find-intents.feature | 74 +++ .../temporary-features/heartbeat.feature | 18 + .../intent-listener.feature | 42 ++ .../temporary-features/intent-results.feature | 160 +++++ .../resources/temporary-features/open.feature | 62 ++ .../private-channels-deprecated.feature | 70 +++ .../private-channels.feature | 150 +++++ .../temporary-features/raise-intents.feature | 100 ++++ .../user-channel-selector.feature | 18 + .../user-channel-sync.feature | 44 ++ .../user-channels-set-by-agent.feature | 41 ++ .../temporary-features/user-channels.feature | 393 ++++++++++++ .../temporary-features/utils.feature | 10 + fdc3-context/pom.xml | 43 +- fdc3-context/scripts/fix-context-imports.sh | 29 + .../finos/fdc3/getagent/TestSpringConfig.java | 2 +- fdc3-schema/pom.xml | 2 +- fdc3-testing/pom.xml | 159 ----- .../org/finos/fdc3/testing/package-info.java | 20 - .../fdc3/testing/steps/GenericSteps.java | 560 ------------------ .../fdc3/testing/support/MatchingUtils.java | 315 ---------- .../finos/fdc3/testing/world/PropsWorld.java | 198 ------- pom.xml | 1 - 33 files changed, 1496 insertions(+), 1271 deletions(-) create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/README.md create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/heartbeat.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/open.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/private-channels-deprecated.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-selector.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-sync.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/user-channels-set-by-agent.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature create mode 100644 fdc3-agent-proxy/src/test/resources/temporary-features/utils.feature create mode 100644 fdc3-context/scripts/fix-context-imports.sh delete mode 100644 fdc3-testing/pom.xml delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java delete mode 100644 fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java diff --git a/README.md b/README.md index 0215913e..0faf31c8 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ This project provides: | `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 | -| `fdc3-testing` | Cucumber step definitions and test utilities for FDC3 conformance testing | ## Requirements diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index b34de535..51dcb4a8 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -18,9 +18,7 @@ UTF-8 11 11 - - ${project.basedir}/../../FDC3/packages/fdc3-agent-proxy/test/features + src/test/resources/temporary-features
@@ -40,9 +38,9 @@ - org.finos.fdc3 - fdc3-testing - ${project.version} + io.github.robmoffat + standard-cucumber-steps + 1.1.0 test 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 index 18691f11..7ee51a85 100644 --- 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 @@ -17,7 +17,6 @@ package org.finos.fdc3.proxy; import org.finos.fdc3.proxy.world.CustomWorld; -import org.finos.fdc3.testing.world.PropsWorld; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -25,6 +24,7 @@ import org.springframework.context.annotation.ScopedProxyMode; import io.cucumber.spring.ScenarioScope; +import io.github.robmoffat.world.PropsWorld; /** * Spring configuration for Cucumber tests. 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 index 0887a731..2f550bd7 100644 --- 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 @@ -24,7 +24,8 @@ import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.ui.ChannelSelector; -import org.finos.fdc3.testing.world.PropsWorld; + +import io.github.robmoffat.world.PropsWorld; /** * A simple channel selector for testing purposes. 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 index cb0d7f8a..863c950d 100644 --- 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 @@ -28,7 +28,7 @@ import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.ui.IntentResolutionChoice; import org.finos.fdc3.api.ui.IntentResolver; -import org.finos.fdc3.testing.world.PropsWorld; +import io.github.robmoffat.world.PropsWorld; /** * A simple intent resolver for testing purposes. 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 index 48577e78..699deb6c 100644 --- 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 @@ -17,7 +17,8 @@ package org.finos.fdc3.proxy.world; import org.finos.fdc3.proxy.support.TestMessaging; -import org.finos.fdc3.testing.world.PropsWorld; + +import io.github.robmoffat.world.PropsWorld; /** * Custom Cucumber World for agent-proxy tests. 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..02314d13 --- /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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameter "{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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "{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" with parameter "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" with parameters "{true}" and "{resultHandler}" + Then "{result}" is an error + And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{true}" + Then "{result}" is an error + + Scenario: Destructured channel methods - broadcast and addContextListener + When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "broadcast", "getCurrentContext" from "{channel1}" + And I call destructured "broadcast" with parameter "{instrumentContext}" + And I call destructured "getCurrentContext" with parameter "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I destructure methods "addContextListener", "broadcast" from "{channel1}" + And I call destructured "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "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..1a5cdbef --- /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" with parameter "{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" with parameter "{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..40f62599 --- /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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "broadcast" with parameter "{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" with parameter "{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" with parameter "one" + And I call "{api}" with "broadcast" with parameter "{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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "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" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + And messaging receives "{fullMetadataMessage}" + Then "{metadatas}" is an array of objects with the following contents + | source.appId | source.instanceId | signature | custom.region | + | cucumber-app | cucumber-instance | test-sig | EMEA | + + Scenario: getCurrentContextWithMetadata returns context and metadata + When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" + And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" + Then "{result}" is an object with the following contents + | context.type | context.name | metadata.source.appId | metadata.traceId | metadata.signature | metadata.custom.key | + | fdc3.instrument | Apple | test-app | test-trace-id | test-signature | value | + + Scenario: getCurrentContextWithMetadata returns null for empty channel + When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + And I refer to "{result}" as "channel1" + And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "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..d7fc546b --- /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" with parameter "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" with parameter "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" with parameters "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" with parameters "OrderFood" and "{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" with parameter "{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" with parameter "{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..798963fd --- /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" with parameters "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" with parameters "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" with parameters "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" with parameters "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..13f8973b --- /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" with parameters "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" with parameters "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" with parameters "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" with parameters "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: Context Data is returned in the result + Given Raise Intent returns a context of "{instrumentContext}" + When I call "{api}" with "raiseIntent" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + And I call "{result}" with "getResultMetadata" + Then "{result}" is an object with the following contents + | source.appId | source.instanceId | traceId | signature | + | some-app | abc123 | my-trace-123 | sig-abc | + + Scenario: getResultMetadata returns DA-generated metadata for a channel result + Given Raise Intent returns a private channel + When I call "{api}" with "raiseIntent" with parameters "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" with parameters "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..795ff1ce --- /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" with parameters "{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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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" + When I call "{api}" with "open" with parameters "{c1}" and "{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 | matches_type | + | chipShop | {null} | trace-open | sig-open | 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..d03f459d --- /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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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" with parameter "{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..8071b50d --- /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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" with parameters "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" with parameters "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" with parameter "{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" with parameters "fdc3.instrument" and "{resultHandler}" + And I call destructured "broadcast" with parameter "{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..f7c05002 --- /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" with parameters "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" with parameters "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" with parameters "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" with parameter "{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" with parameters "{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" with parameter "{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 and custom + Given "intentMetadata" is metadata with traceId "trace-123" and signature "sig-abc" + When I call "{api}" with "raiseIntent" with parameters "Buy" and "{instrumentContext}" and "{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 | payload.metadata.custom.priority | matches_type | + | Buy | fdc3.instrument | trace-123 | sig-abc | high | raiseIntentRequest | + + Scenario: Raising an intent without metadata generates a traceId but omits signature and custom + When I call "{api}" with "raiseIntent" with parameters "Buy" and "{instrumentContext}" + And messaging will have posts + | payload.intent | payload.context.type | payload.metadata.signature | payload.metadata.custom | matches_type | + | Buy | fdc3.instrument | {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" + When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{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 | payload.metadata.custom.priority | payload.app.appId | matches_type | + | fdc3.country | trace-456 | sig-def | high | {null} | raiseIntentForContextRequest | + | fdc3.country | trace-456 | sig-def | 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..845e0a3d --- /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" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "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" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "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" with parameter "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..367fcbad --- /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" with parameters "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" with parameters "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..2e5fdca7 --- /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" with parameter "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" with parameter "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "{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" with parameter "one" + And I call "{api}" with "addContextListener" with parameter "{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" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "{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" with parameters "{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" with parameter "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And I call "{api}" with "broadcast" with parameter "{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" with parameter "one" + And I call "{api}" with "getCurrentChannel" + And I refer to "{result}" as "theChannel" + And messaging receives "{instrumentMessageOne}" + And I call "{theChannel}" with "getCurrentContext" with parameter "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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" with parameter "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" with parameters "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" with parameters "{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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" with parameter "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" with parameter "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" with parameter "{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" with parameter "one" + And I call destructured "broadcast" with parameter "{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" with parameter "one" + And I call destructured "addContextListener" with parameters "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" with parameters "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" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "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 index 760a5da9..bc06ecda 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -19,7 +19,7 @@ 11 v20.11.0 10.2.4 - 2.2.0 + 2.2.3 @@ -64,6 +64,8 @@ ${jdk.source.version} ${jdk.target.version} + + false @@ -86,6 +88,20 @@ + + + clean-generated-sources + generate-sources + + run + + + + + + + + @@ -163,12 +179,29 @@ exec - /bin/sh - ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/context + 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 - rm -f Context.java ContextElement.java 2>/dev/null; for f in *.java; do sed -i '' -e 's/ContextElement/Context/g' "$f"; grep -qE "(private|public) Context |Context get|Context\[\]|ContextFromJsonString" "$f" && sed -i '' 's/^package org.finos.fdc3.context;$/package org.finos.fdc3.context;\ -import org.finos.fdc3.api.context.Context;/' "$f"; done || true + 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 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-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java b/fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/TestSpringConfig.java index c22fed37..ec37feba 100644 --- 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 @@ -16,7 +16,7 @@ package org.finos.fdc3.getagent; -import org.finos.fdc3.testing.world.PropsWorld; +import io.github.robmoffat.world.PropsWorld; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; diff --git a/fdc3-schema/pom.xml b/fdc3-schema/pom.xml index 1d41cb0c..14beb1f1 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -19,7 +19,7 @@ 11 v20.11.0 10.2.4 - 2.2.0 + 2.2.3 diff --git a/fdc3-testing/pom.xml b/fdc3-testing/pom.xml deleted file mode 100644 index d9d21739..00000000 --- a/fdc3-testing/pom.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - 4.0.0 - - org.finos.fdc3 - fdc3-parent - 1.0.0-SNAPSHOT - - - fdc3-testing - FDC3 Cucumber Testing Framework - Cucumber step definitions and utilities for testing FDC3 implementations - - - UTF-8 - 11 - 11 - 7.15.0 - 5.10.1 - 2.16.1 - 1.3.1 - - - - - - org.finos.fdc3 - fdc3-standard - ${project.version} - - - - - org.finos.fdc3 - fdc3-schema - ${project.version} - - - - - io.cucumber - cucumber-java - ${cucumber.version} - - - io.cucumber - cucumber-junit-platform-engine - ${cucumber.version} - - - io.cucumber - cucumber-spring - ${cucumber.version} - - - - - org.springframework - spring-context - 6.1.2 - - - org.springframework - spring-test - 6.1.2 - - - - - org.junit.platform - junit-platform-suite - 1.10.1 - - - org.junit.jupiter - junit-jupiter - ${junit.version} - - - - - commons-jxpath - commons-jxpath - 1.3 - - - - - com.networknt - json-schema-validator - ${networknt.version} - - - - - org.slf4j - slf4j-api - 2.0.9 - - - - - org.glassfish.tyrus - tyrus-server - 2.1.5 - - - org.glassfish.tyrus - tyrus-container-grizzly-server - 2.1.5 - - - - - - - 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 - - - - - - - - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java deleted file mode 100644 index 7b854603..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * FDC3 Cucumber Testing Framework. - *

- * This package provides Cucumber step definitions and utilities for testing FDC3 implementations. - * It is designed to allow reuse of Cucumber feature files from the JavaScript FDC3 repository - * in Java implementations. - *

- * Main components: - *

    - *
  • {@link org.finos.fdc3.testing.world.PropsWorld} - Cucumber World class for test state
  • - *
  • {@link org.finos.fdc3.testing.steps.GenericSteps} - Generic Cucumber step definitions
  • - *
  • {@link org.finos.fdc3.testing.support.MatchingUtils} - Utilities for matching test data
  • - *
  • {@link org.finos.fdc3.testing.agent.SimpleIntentResolver} - Simple intent resolver for testing
  • - *
  • {@link org.finos.fdc3.testing.agent.SimpleChannelSelector} - Simple channel selector for testing
  • - *
- * - * @see FDC3 Standard - */ -package org.finos.fdc3.testing; - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java deleted file mode 100644 index 0bf9939e..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/steps/GenericSteps.java +++ /dev/null @@ -1,560 +0,0 @@ -/** - * 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.testing.steps; - -import static org.finos.fdc3.testing.support.MatchingUtils.doesRowMatch; -import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static org.finos.fdc3.testing.support.MatchingUtils.matchData; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.ArrayList; -import java.lang.reflect.Array; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.finos.fdc3.api.types.EventHandler; -import org.finos.fdc3.api.types.FDC3Event; -import org.finos.fdc3.testing.world.PropsWorld; - -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; - -/** - * Generic Cucumber step definitions for FDC3 testing. - * This is equivalent to the TypeScript generic.steps.ts module. - */ -public class GenericSteps { - - private final PropsWorld world; - - public GenericSteps(PropsWorld world) { - this.world = world; - } - - // ========== Promise Resolution Steps ========== - - @Then("the promise {string} should resolve") - public void thePromiseShouldResolve(String field) { - try { - Object promise = handleResolve(field, world); - Object result = resolvePromise(promise); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @Then("the promise {string} should resolve within 10 seconds") - public void thePromiseShouldResolveWithin10Seconds(String field) throws Exception { - try { - Object promise = handleResolve(field, world); - Object result = resolvePromiseWithTimeout(promise, 10, TimeUnit.SECONDS); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - // ========== Method Invocation Steps ========== - - @When("I call {string} with {string}") - public void iCallWith(String field, String fnName) { - try { - Object object = handleResolve(field, world); - Object result = invokeMethod(object, fnName); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} with parameter {string}") - public void iCallWithParameter(String field, String fnName, String param) { - try { - Object object = handleResolve(field, world); - Object paramValue = handleResolve(param, world); - Object result = invokeMethod(object, fnName, paramValue); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} with parameters {string} and {string}") - public void iCallWithTwoParameters(String field, String fnName, String param1, String param2) { - try { - Object object = handleResolve(field, world); - Object paramValue1 = handleResolve(param1, world); - Object paramValue2 = handleResolve(param2, world); - Object result = invokeMethod(object, fnName, paramValue1, paramValue2); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} with parameters {string} and {string} and {string}") - public void iCallWithThreeParameters(String field, String fnName, String param1, String param2, String param3) { - try { - Object object = handleResolve(field, world); - Object paramValue1 = handleResolve(param1, world); - Object paramValue2 = handleResolve(param2, world); - Object paramValue3 = handleResolve(param3, world); - Object result = invokeMethod(object, fnName, paramValue1, paramValue2, paramValue3); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - // ========== Reference/Alias Steps ========== - - @When("I refer to {string} as {string}") - public void iReferToAs(String from, String to) { - world.set(to, handleResolve(from, world)); - } - - // ========== Array Matching Steps ========== - - /** - * Convert an object to a List. Handles both arrays and Lists. - */ - private List toList(Object obj) { - if (obj == null) { - return Collections.emptyList(); - } - if (obj instanceof List) { - return (List) obj; - } - if (obj.getClass().isArray()) { - // Convert array to list - int length = Array.getLength(obj); - List list = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - list.add(Array.get(obj, i)); - } - return list; - } - throw new IllegalArgumentException("Expected array or List, but got: " + obj.getClass().getName()); - } - - @Then("{string} is an array of objects with the following contents") - public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { - Object resolved = handleResolve(field, world); - List data = toList(resolved); - matchData(world, data, dt); - } - - @Then("{string} is an array of objects with length {string}") - public void isAnArrayOfObjectsWithLength(String field, String lengthField) { - Object resolved = handleResolve(field, world); - List data = toList(resolved); - String amt = (String) handleResolve(lengthField, world); - assertEquals(Integer.parseInt(amt), data.size()); - } - - @Then("{string} is an array of strings with the following values") - public void isAnArrayOfStringsWithValues(String field, DataTable dt) { - Object resolved = handleResolve(field, world); - List data = toList(resolved); - List> values = data.stream() - .map(s -> Map.of("value", s)) - .collect(Collectors.toList()); - matchData(world, values, dt); - } - - // ========== Object Matching Steps ========== - - @Then("{string} is an object with the following contents") - public void isAnObjectWithContents(String field, DataTable params) { - List> table = params.asMaps(); - Object data = handleResolve(field, world); - assertTrue(doesRowMatch(world, table.get(0), data)); - } - - // ========== Null/Boolean/Undefined Checks ========== - - @Then("{string} is null") - public void isNull(String field) { - assertNull(handleResolve(field, world)); - } - - @Then("{string} is not null") - public void isNotNull(String field) { - assertNotNull(handleResolve(field, world)); - } - - @Then("{string} is true") - public void isTrue(String field) { - Object value = handleResolve(field, world); - assertTrue(isTruthy(value)); - } - - @Then("{string} is false") - public void isFalse(String field) { - Object value = handleResolve(field, world); - assertFalse(isTruthy(value)); - } - - @Then("{string} is undefined") - public void isUndefined(String field) { - // In Java, we treat undefined as null or non-existent in the props map - Object value = handleResolve(field, world); - assertNull(value); - } - - @Then("{string} is empty") - public void isEmpty(String field) { - List data = (List) handleResolve(field, world); - assertTrue(data.isEmpty()); - } - - // ========== Equality Check ========== - - @Then("{string} is {string}") - public void fieldIsValue(String field, String expected) { - Object fieldValue = handleResolve(field, world); - Object expectedValue = handleResolve(expected, world); - assertEquals(String.valueOf(expectedValue), String.valueOf(fieldValue)); - } - - // ========== Error Checks ========== - - @Then("{string} is an error with message {string}") - public void isAnErrorWithMessage(String field, String errorType) { - Object value = handleResolve(field, world); - assertTrue(value instanceof Throwable, "Expected a Throwable but got: " + value); - - // Extract the root cause message - exceptions may be wrapped multiple times - Throwable t = (Throwable) value; - String message = getRootCauseMessage(t); - assertEquals(errorType, message); - } - - /** - * Gets the message from the root cause of a potentially wrapped exception chain. - */ - private String getRootCauseMessage(Throwable t) { - Throwable root = t; - while (root.getCause() != null && root.getCause() != root) { - root = root.getCause(); - } - return root.getMessage(); - } - - @Then("{string} is an error") - public void isAnError(String field) { - Object value = handleResolve(field, world); - assertTrue(value instanceof Throwable); - } - - // ========== Invocation Counter ========== - - @Given("{string} is a invocation counter into {string}") - public void isAnInvocationCounter(String handlerName, String counterField) { - world.set(counterField, 0); - EventHandler eh = new EventHandler() { - - @Override - public void handleEvent(FDC3Event event) { - int amount = (Integer) world.get(counterField); - amount++; - world.set(counterField, amount); - } - }; - world.set(handlerName, eh); - } - - // ========== Function Creation ========== - - @Given("{string} is a function which returns a promise of {string}") - public void isAFunctionReturningPromise(String fnName, String valueField) { - Object value = handleResolve(valueField, world); - world.set(fnName, (Supplier>) () -> - CompletableFuture.completedFuture(value)); - } - - // ========== Wait Step ========== - - @Given("we wait for a period of {string} ms") - public void weWaitForPeriod(String ms) throws InterruptedException { - Thread.sleep(Long.parseLong(ms)); - } - - // ========== Schema Loading ========== - - @Given("schemas loaded") - public void schemasLoaded() { - Map schemas = new HashMap<>(); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - - // Find the fdc3-schema module's npm-work directory - // This works when running from the project root or any submodule - Path schemaDir = findSchemaDirectory(); - - if (schemaDir == null || !Files.exists(schemaDir)) { - world.log("Schema directory not found. Run 'mvn compile' on fdc3-schema first."); - return; - } - - // Load all API schemas - Path apiDir = schemaDir.resolve("api"); - if (Files.exists(apiDir)) { - try (Stream files = Files.list(apiDir)) { - files.filter(p -> p.toString().endsWith(".json")) - .forEach(file -> { - try { - String schemaContent = Files.readString(file); - JsonSchema schema = factory.getSchema(schemaContent); - // Use the schema $id or filename as the key - String schemaId = extractSchemaId(schemaContent, file.getFileName().toString()); - schemas.put(schemaId, schema); - } catch (IOException e) { - world.log("Error loading schema " + file + ": " + e.getMessage()); - } - }); - } catch (IOException e) { - world.log("Error reading schema directory: " + e.getMessage()); - } - } - - // Load context schema - Path contextSchemaPath = findContextSchemaDirectory(); - if (contextSchemaPath != null) { - Path contextSchema = contextSchemaPath.resolve("context").resolve("context.schema.json"); - if (Files.exists(contextSchema)) { - try { - String schemaContent = Files.readString(contextSchema); - JsonSchema schema = factory.getSchema(schemaContent); - schemas.put("context", schema); - world.log("Loaded context schema"); - } catch (IOException e) { - world.log("Error loading context schema: " + e.getMessage()); - } - } - } - - world.set("schemas", schemas); - world.log("Loaded " + schemas.size() + " schemas"); - } - - /** - * Find the schema directory by checking various possible locations. - */ - private Path findSchemaDirectory() { - // Possible locations relative to current working directory - String[] possiblePaths = { - "fdc3-schema/target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas", - "../fdc3-schema/target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas", - "target/npm-work/node_modules/@finos/fdc3-schema/dist/schemas" - }; - - for (String pathStr : possiblePaths) { - Path path = Paths.get(pathStr); - if (Files.exists(path)) { - return path; - } - } - return null; - } - - /** - * Find the context schema directory. - */ - private Path findContextSchemaDirectory() { - String[] possiblePaths = { - "fdc3-schema/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", - "../fdc3-schema/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", - "fdc3-context/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas", - "../fdc3-context/target/npm-work/node_modules/@finos/fdc3-context/dist/schemas" - }; - - for (String pathStr : possiblePaths) { - Path path = Paths.get(pathStr); - if (Files.exists(path)) { - return path; - } - } - return null; - } - - /** - * Extract the schema ID from the schema content or use the filename. - */ - private String extractSchemaId(String schemaContent, String filename) { - // Try to extract $id from the schema - // Simple regex approach - could use Jackson for more robust parsing - int idIndex = schemaContent.indexOf("\"$id\""); - if (idIndex >= 0) { - int colonIndex = schemaContent.indexOf(":", idIndex); - int quoteStart = schemaContent.indexOf("\"", colonIndex + 1); - int quoteEnd = schemaContent.indexOf("\"", quoteStart + 1); - if (quoteStart >= 0 && quoteEnd > quoteStart) { - return schemaContent.substring(quoteStart + 1, quoteEnd); - } - } - // Fall back to filename without extension - return filename.replace(".schema.json", "").replace(".json", ""); - } - - // ========== Helper Methods ========== - - /** - * Resolve a promise/CompletionStage to its value. - */ - private Object resolvePromise(Object promise) throws Exception { - if (promise instanceof CompletionStage) { - return ((CompletionStage) promise).toCompletableFuture().get(); - } else if (promise instanceof CompletableFuture) { - return ((CompletableFuture) promise).get(); - } - return promise; - } - - /** - * Resolve a promise with a timeout. - */ - private Object resolvePromiseWithTimeout(Object promise, long timeout, TimeUnit unit) throws Exception { - if (promise instanceof CompletionStage) { - return ((CompletionStage) promise).toCompletableFuture().get(timeout, unit); - } else if (promise instanceof CompletableFuture) { - return ((CompletableFuture) promise).get(timeout, unit); - } - return promise; - } - - /** - * Invoke a method on an object by name. - */ - private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { - Method method = findMethod(target.getClass(), methodName, args); - if (method == null) { - throw new NoSuchMethodException("Method not found: " + methodName); - } - - method.setAccessible(true); - Object result = method.invoke(target, args); - - // If the result is a CompletionStage, resolve it - return resolvePromise(result); - } - - - public static Method findMethod( - Class targetClass, - String name, - Object... args - ) { - Method bestMatch = null; - - for (Method method : targetClass.getMethods()) { - if (!method.getName().equals(name)) continue; - - Class[] paramTypes = method.getParameterTypes(); - if (paramTypes.length != args.length) continue; - - if (isCompatible(paramTypes, args)) { - if (bestMatch == null || - isMoreSpecific(paramTypes, bestMatch.getParameterTypes())) { - bestMatch = method; - } - } - } - - return bestMatch; - } - - private static boolean isCompatible(Class[] paramTypes, Object[] args) { - for (int i = 0; i < paramTypes.length; i++) { - if (args[i] == null) { - if (paramTypes[i].isPrimitive()) return false; - continue; - } - - Class argType = args[i].getClass(); - if (!wrap(paramTypes[i]).isAssignableFrom(argType)) { - return false; - } - } - return true; - } - - private static boolean isMoreSpecific(Class[] a, Class[] b) { - for (int i = 0; i < a.length; i++) { - if (a[i] != b[i] && b[i].isAssignableFrom(a[i])) { - return true; - } - } - return false; - } - - private static Class wrap(Class type) { - if (!type.isPrimitive()) return type; - if (type == int.class) return Integer.class; - if (type == long.class) return Long.class; - if (type == boolean.class) return Boolean.class; - if (type == double.class) return Double.class; - if (type == float.class) return Float.class; - if (type == char.class) return Character.class; - if (type == byte.class) return Byte.class; - if (type == short.class) return Short.class; - return type; - } - - /** - * Check if a value is truthy (JavaScript-style). - */ - private boolean isTruthy(Object value) { - if (value == null) { - return false; - } - if (value instanceof Boolean) { - return (Boolean) value; - } - if (value instanceof Number) { - return ((Number) value).doubleValue() != 0; - } - if (value instanceof String) { - return !((String) value).isEmpty(); - } - return true; - } -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java deleted file mode 100644 index 80e0105f..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/support/MatchingUtils.java +++ /dev/null @@ -1,315 +0,0 @@ -/** - * 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.testing.support; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.finos.fdc3.schema.SchemaConverter; -import org.finos.fdc3.testing.world.PropsWorld; -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.JXPathNotFoundException; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; - -import io.cucumber.datatable.DataTable; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Utility class for matching and resolving test data. - * This is equivalent to the TypeScript matching.ts module. - */ -public final class MatchingUtils { - - private static final SchemaConverter converter = new SchemaConverter(); - private static final ObjectMapper objectMapper = converter.getObjectMapper(); - - private MatchingUtils() { - // Utility class - } - - private static Object extractFromWorld(Object world, String expression) { - // Use JXPath to resolve the value from props - try { - JXPathContext context = JXPathContext.newContext(world); - context.setLenient(true); - String xpathName = "/" + expression.replaceAll("\\.", "/"); - // Convert .length to count(/path) for XPath - xpathName = xpathName.replaceAll("(/[^/]+)/length$", "count($1)"); - // Convert 0-based array indexes to 1-based for JXPath (e.g., [0] -> [1]) - Matcher matcher = Pattern.compile("\\[(\\d+)\\]").matcher(xpathName); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - int index = Integer.parseInt(matcher.group(1)); - matcher.appendReplacement(sb, "[" + (index + 1) + "]"); - } - matcher.appendTail(sb); - xpathName = sb.toString(); - Object result = context.getValue(xpathName); - // Unwrap Optional if needed - if (result instanceof java.util.Optional) { - result = ((java.util.Optional) result).orElse(null); - } - // Convert numbers to rounded strings with 0 decimal places - if (result instanceof Number) { - return String.valueOf(Math.round(((Number) result).doubleValue())); - } - return result; - } catch (JXPathNotFoundException e) { - return null; - } - } - - /** - * Resolve a field reference to its actual value. - *

- * If the name is wrapped in braces like {field.path}, it will be resolved - * using JSONPath against the PropsWorld props. Special values like {null}, - * {true}, {false}, and numeric values are handled specially. - * - * @param name the field reference or literal value - * @param world the PropsWorld containing test state - * @return the resolved value - */ - public static Object handleResolve(String name, PropsWorld world) { - if (name.startsWith("{") && name.endsWith("}")) { - String stripped = name.substring(1, name.length() - 1); - - // Handle special values - if ("null".equals(stripped)) { - return null; - } else if ("true".equals(stripped)) { - return true; - } else if ("false".equals(stripped)) { - return false; - } else if (isNumeric(stripped)) { - return Double.parseDouble(stripped); - } else { - return extractFromWorld(world, stripped); - } - } else { - return name; - } - } - - /** - * Check if a string is numeric. - * - * @param str the string to check - * @return true if the string represents a number - */ - private static boolean isNumeric(String str) { - try { - Double.parseDouble(str); - return true; - } catch (NumberFormatException e) { - return false; - } - } - - private static JsonSchema findSchema(Map schemas, String name) { - for (Map.Entry entry : schemas.entrySet()) { - if (entry.getKey().endsWith(name + ".schema.json")) { - return entry.getValue(); - } - } - return null; - } - - /** - * Check if a table row matches the given data object. - * - * @param world the PropsWorld for resolving references - * @param row the table row as a map of field names to expected values - * @param data the actual data object to match against - * @return true if the row matches the data - */ - public static boolean doesRowMatch(PropsWorld world, Map row, Object data) { - for (Map.Entry entry : row.entrySet()) { - String field = entry.getKey(); - String expected = entry.getValue(); - - if (field.endsWith("matches_type")) { - // Schema validation mode - Object valData = data; - - if (field.length() > "matches_type".length()) { - // Extract path before matches_type - String path = field.substring(0, field.length() - "matches_type".length() - 1); - valData = extractFromWorld(world, path); - } - - // Validate against schema - @SuppressWarnings("unchecked") - Map schemas = (Map) world.get("schemas"); - if (schemas == null) { - world.log("No schemas loaded"); - return false; - } - - JsonSchema schema = findSchema(schemas,expected); - - if (schema == null) { - world.log("No schema found for " + expected); - return false; - } - - try { - JsonNode dataNode = objectMapper.valueToTree(valData); - var errors = schema.validate(dataNode); - if (!errors.isEmpty()) { - world.log("Validation failed: " + errors); - return false; - } - } catch (Exception e) { - world.log("Validation error: " + e.getMessage()); - return false; - } - } else { - // Field value comparison using JXPath - try { - Object found = extractFromWorld(data, field); - Object resolved = handleResolve(expected, world); - - if (!Objects.equals(asString(found), asString(resolved))) { - world.log(String.format( - "Match failed on %s: '%s' vs '%s'", field, found, resolved)); - return false; - } - } catch (JXPathNotFoundException e) { - world.log("Path not found: " + field); - return false; - } catch (Exception e) { - world.log("Error: " + e.getMessage()); - return false; - } - } - } - - return true; - } - - /** - * Convert a value to string for comparison. - */ - private static String asString(Object value) { - return value == null ? null : String.valueOf(value); - } - - /** - * Find the index of a matching row in the list. - * - * @param world the PropsWorld for resolving references - * @param rows the list of expected rows - * @param data the actual data object to find - * @return the index of the matching row, or -1 if not found - */ - public static int indexOf(PropsWorld world, List> rows, Object data) { - for (int i = 0; i < rows.size(); i++) { - if (doesRowMatch(world, rows.get(i), data)) { - return i; - } - } - return -1; - } - - /** - * Match an array of data against a Cucumber DataTable. - * - * @param world the PropsWorld for resolving references - * @param actual the actual array data - * @param dt the expected DataTable - */ - public static void matchData(PropsWorld world, List actual, DataTable dt) { - List> tableData = dt.asMaps(); - int rowCount = tableData.size(); - - world.log(String.format("result %s length %d", - formatJson(actual), actual.size())); - - assertEquals(rowCount, actual.size(), - "Array length mismatch"); - - List resultCopy = new ArrayList<>(actual); - List unmatched = new ArrayList<>(); - - int row = 0; - for (Object item : resultCopy) { - Map matchingRow = tableData.get(row); - row++; - if (!doesRowMatch(world, matchingRow, item)) { - world.log("Couldn't match row: " + formatJson(item)); - unmatched.add(item); - } - } - - assertTrue(unmatched.isEmpty(), - "Some rows could not be matched: " + formatJson(unmatched)); - } - - /** - * Format an object as JSON for logging. - */ - private static String formatJson(Object obj) { - try { - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); - } catch (JsonProcessingException e) { - return String.valueOf(obj); - } - } - - /** - * Load JSON schemas from a directory. - * - * @param schemaDir the directory containing schema files - * @return a map of schema names to JsonSchema objects - */ - public static Map loadSchemas(String schemaDir) { - Map schemas = new java.util.HashMap<>(); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - - java.io.File dir = new java.io.File(schemaDir); - if (dir.exists() && dir.isDirectory()) { - java.io.File[] files = dir.listFiles((d, name) -> name.endsWith(".json")); - if (files != null) { - for (java.io.File file : files) { - try { - String content = java.nio.file.Files.readString(file.toPath()); - JsonSchema schema = factory.getSchema(content); - String schemaName = file.getName().replace(".schema.json", ""); - schemas.put(schemaName, schema); - } catch (Exception e) { - // Log and continue - } - } - } - } - - return schemas; - } -} - diff --git a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java b/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java deleted file mode 100644 index 5f44c552..00000000 --- a/fdc3-testing/src/main/java/org/finos/fdc3/testing/world/PropsWorld.java +++ /dev/null @@ -1,198 +0,0 @@ -/** - * 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.testing.world; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import io.cucumber.java.Scenario; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Cucumber World class that holds test state in a props map. - * This is equivalent to the TypeScript PropsWorld class. - */ -public class PropsWorld implements Map{ - - private static final Logger logger = LoggerFactory.getLogger(PropsWorld.class); - - private final Map props = new HashMap<>(); - private Scenario scenario; - - /** - * Get the props map containing all test state. - * - * @return the props map - */ - public Map getProps() { - return props; - } - - /** - * Get a property value by key. - * - * @param key the property key - * @return the property value, or null if not found - */ - public Object get(String key) { - return props.get(key); - } - - /** - * Set a property value. - * - * @param key the property key - * @param value the property value - */ - public void set(String key, Object value) { - props.put(key, value); - } - - /** - * Check if a property exists. - * - * @param key the property key - * @return true if the property exists - */ - public boolean has(String key) { - return props.containsKey(key); - } - - /** - * Set the Cucumber scenario for logging. - * This should be called from a @Before hook. - * - * @param scenario the current Cucumber scenario - */ - public void setScenario(Scenario scenario) { - this.scenario = scenario; - } - - /** - * Get the current scenario. - * - * @return the current Cucumber scenario - */ - public Scenario getScenario() { - return scenario; - } - - /** - * Log a message to the Cucumber report. - * This is equivalent to this.log() in TypeScript Cucumber World. - * - * @param message the message to log - */ - public void log(String message) { - // Log to SLF4J for console output - logger.info(message); - - // Log to Cucumber report if scenario is available - if (scenario != null) { - scenario.log(message); - } - } - - /** - * Attach content to the Cucumber report. - * - * @param data the data to attach - * @param mediaType the MIME type of the data - * @param name optional name for the attachment - */ - public void attach(byte[] data, String mediaType, String name) { - if (scenario != null) { - scenario.attach(data, mediaType, name); - } - } - - /** - * Attach text content to the Cucumber report. - * - * @param data the text to attach - * @param mediaType the MIME type (e.g., "text/plain", "application/json") - */ - public void attach(String data, String mediaType) { - if (scenario != null) { - scenario.attach(data, mediaType, null); - } - } - - @Override - public int size() { - return props.size(); - } - - @Override - public boolean isEmpty() { - return props.size() == 0; - } - - @Override - public boolean containsKey(Object key) { - return props.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return props.containsValue(value); - - } - - @Override - public Object put(String key, Object value) { - return props.put(key, value); - } - - @Override - public Object remove(Object key) { - return props.remove(key); - } - - @Override - public void putAll(Map m) { - props.putAll(m); - } - - @Override - public void clear() { - props.clear(); - } - - @Override - public Set keySet() { - return props.keySet(); - } - - @Override - public Collection values() { - return props.values(); - } - - @Override - public Set> entrySet() { - return props.entrySet(); - } - - @Override - public Object get(Object key) { - return props.get(key); - } -} diff --git a/pom.xml b/pom.xml index 6e4e98cf..55b32632 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,6 @@ fdc3-standard fdc3-schema fdc3-context - fdc3-testing fdc3-agent-proxy fdc3-get-agent fdc3-example-app From 97248a715292426d0cf4299020892b6692bd6431 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Thu, 21 May 2026 09:58:47 +0100 Subject: [PATCH 55/65] fixed package names --- .../test/java/org/finos/fdc3/proxy/TestSpringConfig.java | 2 +- .../test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java | 6 +++--- .../test/java/org/finos/fdc3/proxy/steps/IntentSteps.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 index 7ee51a85..b586b3bb 100644 --- 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 @@ -36,7 +36,7 @@ @Configuration @ComponentScan(basePackages = { "org.finos.fdc3.proxy.steps", - "org.finos.fdc3.testing.steps" + "io.github.robmoffat.steps" }) public class TestSpringConfig { 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 index 8f852177..08e52e27 100644 --- 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 @@ -16,8 +16,8 @@ package org.finos.fdc3.proxy.steps; -import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; -import static org.finos.fdc3.testing.support.MatchingUtils.matchData; +import static io.github.robmoffat.support.MatchingUtils.handleResolve; +import static io.github.robmoffat.support.MatchingUtils.matchData; import java.lang.reflect.Method; import java.util.ArrayList; @@ -32,7 +32,7 @@ import org.finos.fdc3.api.types.FDC3Event; import org.finos.fdc3.proxy.support.ContextMap; import org.finos.fdc3.proxy.world.CustomWorld; -import org.finos.fdc3.testing.steps.GenericSteps; +import io.github.robmoffat.steps.GenericSteps; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; 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 index 6f8dd6ce..4d198ef8 100644 --- 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 @@ -42,7 +42,7 @@ import io.cucumber.java.en.Given; -import static org.finos.fdc3.testing.support.MatchingUtils.handleResolve; +import static io.github.robmoffat.support.MatchingUtils.handleResolve; /** * Cucumber step definitions for intent-related tests. From 8b82d50c89405ffdefd3122d20890eec7f9d4cee Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Thu, 21 May 2026 14:55:06 +0100 Subject: [PATCH 56/65] checking in features --- .../temporary-features/app-channels.feature | 42 +++--- .../temporary-features/app-metadata.feature | 4 +- .../temporary-features/broadcast.feature | 28 ++-- .../temporary-features/find-intents.feature | 12 +- .../intent-listener.feature | 8 +- .../temporary-features/intent-results.feature | 38 +++--- .../resources/temporary-features/open.feature | 18 +-- .../private-channels-deprecated.feature | 12 +- .../private-channels.feature | 26 ++-- .../temporary-features/raise-intents.feature | 38 +++--- .../user-channel-sync.feature | 12 +- .../user-channels-set-by-agent.feature | 4 +- .../temporary-features/user-channels.feature | 120 ++++++------------ 13 files changed, 157 insertions(+), 205 deletions(-) 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 index 02314d13..5da42e88 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature @@ -10,10 +10,10 @@ Feature: Channel Listeners Support Given "resultHandler" pipes context to "contexts" Scenario: Configuring two context listeners should mean they both pick up data - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -26,9 +26,9 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Unsubscribing a context listener prevents it collecting data. - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -40,9 +40,9 @@ Feature: Channel Listeners Support 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" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameter "{resultHandler}" + 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 @@ -56,9 +56,9 @@ Feature: Channel Listeners Support 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" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{resultHandler}" + 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 @@ -71,20 +71,20 @@ Feature: Channel Listeners Support | channel-name | {null} | addContextListenerRequest | Scenario: Passing invalid arguments to an app channel's addContextListener fn throws an error - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + 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" with parameters "{true}" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" using arguments "{true}" and "{resultHandler}" Then "{result}" is an error - And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{true}" + 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" with parameter "channel-name" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -95,21 +95,21 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Destructured getCurrentContext after broadcast - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + 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" with parameter "{instrumentContext}" - And I call destructured "getCurrentContext" with parameter "fdc3.instrument" + 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" with parameter "channel-name" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -117,9 +117,9 @@ Feature: Channel Listeners Support Scenario: App channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "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 | 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 index 1a5cdbef..ebcc4f71 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature @@ -6,7 +6,7 @@ Feature: Desktop Agent Information And app "chipShop/c1" Scenario: Getting App metadata - When I call "{api}" with "getAppMetadata" with parameter "{c1}" + 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 | @@ -24,7 +24,7 @@ Feature: Desktop Agent Information | cucumber-app | cucumber-instance | Scenario: Getting instance information - When I call "{api}" with "findInstances" with parameter "{c1}" + When I call "{api}" with "findInstances" using argument "{c1}" Then "{result}" is an array of objects with the following contents | appId | instanceId | | One | 1 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature index 40f62599..051a25c2 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature @@ -9,21 +9,21 @@ Feature: Broadcasting Given "instrumentContext" is a "fdc3.instrument" context Scenario: Broadcasting on a named app channel - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" + 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" with parameter "{instrumentContext}" + 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" with parameter "one" - And I call "{api}" with "broadcast" with parameter "{instrumentContext}" + 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 | @@ -32,9 +32,9 @@ Feature: Broadcasting Scenario: Context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "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 | @@ -46,25 +46,25 @@ Feature: Broadcasting 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" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | custom.region | | cucumber-app | cucumber-instance | test-sig | EMEA | Scenario: getCurrentContextWithMetadata returns context and metadata - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" - And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" + 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 | metadata.custom.key | | fdc3.instrument | Apple | test-app | test-trace-id | test-signature | value | Scenario: getCurrentContextWithMetadata returns null for empty channel - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" + 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 index d7fc546b..fce0f0aa 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature @@ -13,7 +13,7 @@ Feature: Basic Intents Support And "crazyContext" is a "fdc3.unsupported" context Scenario: Find Intent can return the same intent with multiple apps - When I call "{api}" with "findIntent" with parameter "Buy" + When I call "{api}" with "findIntent" using argument "Buy" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -26,14 +26,14 @@ Feature: Basic Intents Support | Buy | findIntentRequest | Scenario: Find Intent can return an error when an intent doesn't match - When I call "{api}" with "findIntent" with parameter "Bob" + 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" with parameters "Buy" and "{instrumentContext}" + When I call "{api}" with "findIntent" using arguments "Buy" and "{instrumentContext}" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -45,7 +45,7 @@ Feature: Basic Intents Support | Buy | fdc3.instrument | AAPL | findIntentRequest | Scenario: Find Intent can filter by generic result type - When I call "{api}" with "findIntent" with parameters "OrderFood" and "{empty}" and "channel" + When I call "{api}" with "findIntent" using arguments "OrderFood", "{empty}", and "channel" Then "{result.intent}" is an object with the following contents | name | | OrderFood | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | OrderFood | channel | findIntentRequest | Scenario: Find Intents By Context - When I call "{api}" with "findIntentsByContext" with parameter "{instrumentContext}" + 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 | @@ -67,7 +67,7 @@ Feature: Basic Intents Support | 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" with parameter "{crazyContext}" + 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 | 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 index 798963fd..bca04b89 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature @@ -8,7 +8,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Work Given "resultHandler" pipes intent to "intents" - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -19,7 +19,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Context) Given "resultHandler" returns a context item - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -27,7 +27,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Channel) Given "resultHandler" returns a channel - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -35,7 +35,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return A Void Result Given "resultHandler" returns a void promise - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | 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 index 13f8973b..266232d0 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature @@ -8,17 +8,17 @@ Feature: Intents Can Return Different Results Scenario: Raise Intent times out Given Raise Intent times out - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + 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" with parameters "OrderFood" and "{instrumentContext}" + 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -26,7 +26,7 @@ Feature: Intents Can Return Different Results | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | Scenario: Raising An intent With The App Parameter - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" + 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 | @@ -36,7 +36,7 @@ Feature: Intents Can Return Different Results Scenario: Context Data is returned in the result Given Raise Intent returns a context of "{instrumentContext}" - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | @@ -47,7 +47,7 @@ Feature: Intents Can Return Different Results Scenario: App Channel is returned in the result Given Raise Intent returns an app channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -58,7 +58,7 @@ Feature: Intents Can Return Different Results Scenario: User Channel is returned in the result Given Raise Intent returns a user channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -69,7 +69,7 @@ Feature: Intents Can Return Different Results Scenario: Private Channel is returned in the result Given Raise Intent returns a private channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -81,7 +81,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -93,7 +93,7 @@ Feature: Intents Can Return Different Results Scenario: Destructured raiseIntent with app parameter When I destructure method "raiseIntent" from "{api}" - And I call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" + 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 | @@ -104,7 +104,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -117,7 +117,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -129,23 +129,23 @@ Feature: Intents Can Return Different Results Scenario: getResultMetadata returns DA-generated metadata for a context result Given Raise Intent returns a context of "{instrumentContext}" - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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" - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + 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 | - | some-app | abc123 | my-trace-123 | sig-abc | + | 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" with parameters "OrderFood" and "{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 | @@ -153,7 +153,7 @@ Feature: Intents Can Return Different Results Scenario: getResultMetadata returns DA-generated metadata for a void result Given Raise Intent returns no result - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature index 795ff1ce..5aa2c5a9 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature @@ -7,7 +7,7 @@ Feature: Desktop Agent Information And "instrumentContext" is a "fdc3.instrument" context Scenario: Open An App - When I call "{api}" with "open" with parameters "{c1}" and "{instrumentContext}" + When I call "{api}" with "open" using arguments "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -16,7 +16,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Open An App Using App ID - When I call "{api}" with "open" with parameters "chipShop" and "{instrumentContext}" + When I call "{api}" with "open" using arguments "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -25,7 +25,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Opening a non-existent App - When I call "{api}" with "open" with parameters "nonExistent" and "{instrumentContext}" + 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 | @@ -33,7 +33,7 @@ Feature: Desktop Agent Information Scenario: Open An App - Destructured When I destructure method "open" from "{api}" - And I call destructured "open" with parameters "{c1}" and "{instrumentContext}" + And I call destructured "open" using arguments "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -43,7 +43,7 @@ Feature: Desktop Agent Information Scenario: Open An App Using App ID - Destructured When I destructure method "open" from "{api}" - And I call destructured "open" with parameters "chipShop" and "{instrumentContext}" + And I call destructured "open" using arguments "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -52,11 +52,11 @@ Feature: Desktop Agent Information | 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" - When I call "{api}" with "open" with parameters "{c1}" and "{null}" and "{openMetadata}" + 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 | matches_type | - | chipShop | {null} | trace-open | sig-open | openRequest | + | 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 index d03f459d..0f36b274 100644 --- 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 @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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" @@ -22,7 +22,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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 @@ -31,7 +31,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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" @@ -43,7 +43,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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 @@ -52,7 +52,7 @@ Feature: Basic Private Channels Support 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" with parameter "{voidHandler}" + 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" @@ -64,7 +64,7 @@ Feature: Basic Private Channels Support 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" with parameter "{voidHandler}" + 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 index 8071b50d..e937fdac 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{contextHandler}" + 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 @@ -20,7 +20,7 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -28,7 +28,7 @@ Feature: Basic Private Channels Support Scenario: Private channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{privateChannel}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -39,7 +39,7 @@ Feature: Basic Private Channels Support 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" with parameters "addContextListener" and "{typesHandler}" + 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" @@ -51,7 +51,7 @@ Feature: Basic Private Channels Support 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" with parameters "addContextListener" and "{typesHandler}" + 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 @@ -60,7 +60,7 @@ Feature: Basic Private Channels Support 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" with parameters "unsubscribe" and "{typesHandler}" + 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" @@ -72,7 +72,7 @@ Feature: Basic Private Channels Support 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" with parameters "unsubscribe" and "{typesHandler}" + 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 @@ -84,7 +84,7 @@ Feature: Basic Private Channels Support 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" with parameters "{null}" and "{typesHandler}" + 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}" @@ -93,7 +93,7 @@ Feature: Basic Private Channels Support 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" with parameters "disconnect" and "{voidHandler}" + 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" @@ -105,14 +105,14 @@ Feature: Basic Private Channels Support 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" with parameters "disconnect" and "{voidHandler}" + 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" with parameter "{instrumentContext}" + 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 | @@ -135,8 +135,8 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{resultHandler}" - And I call destructured "broadcast" with parameter "{instrumentContext}" + 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 | 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 index f7c05002..d79bbc1f 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature @@ -18,7 +18,7 @@ Feature: Basic Intents Support The intent resolver will just take the first matching application that would resolve the intent. - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + 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 | @@ -28,14 +28,14 @@ Feature: Basic Intents Support | 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" with parameters "OrderFood" and "{cancelContext}" + 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" with parameters "Buy" and "{instrumentContext}" + 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 | @@ -47,7 +47,7 @@ Feature: Basic Intents Support The intent resolver will just take the first matching application that would resolve an intent. - When I call "{api}" with "raiseIntentForContext" with parameter "{instrumentContext}" + When I call "{api}" with "raiseIntentForContext" using argument "{instrumentContext}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | chipShop | c1 | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | fdc3.instrument | AAPL | c1 | raiseIntentRequest | Scenario: Raising Intent By Context exactly right, so the resolver isn't required - When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{t1}" + 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 | @@ -66,35 +66,35 @@ Feature: Basic Intents Support | 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" with parameter "{cancelContext}" + 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 and custom - Given "intentMetadata" is metadata with traceId "trace-123" and signature "sig-abc" - When I call "{api}" with "raiseIntent" with parameters "Buy" and "{instrumentContext}" and "{null}" and "{intentMetadata}" + 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 | payload.metadata.custom.priority | matches_type | - | Buy | fdc3.instrument | trace-123 | sig-abc | high | raiseIntentRequest | + | 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" with parameters "Buy" and "{instrumentContext}" + When I call "{api}" with "raiseIntent" using arguments "Buy" and "{instrumentContext}" And messaging will have posts - | payload.intent | payload.context.type | payload.metadata.signature | payload.metadata.custom | matches_type | - | Buy | fdc3.instrument | {null} | {null} | raiseIntentRequest | + | 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" - When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{null}" and "{intentMetadata}" + 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 | payload.metadata.custom.priority | payload.app.appId | matches_type | - | fdc3.country | trace-456 | sig-def | high | {null} | raiseIntentForContextRequest | - | fdc3.country | trace-456 | sig-def | high | chipShop | raiseIntentRequest | + | 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-sync.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channel-sync.feature index 845e0a3d..ca11b6a1 100644 --- 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 @@ -11,14 +11,14 @@ Feature: Updating User Channel State Scenario: Joining A User Channel Receives Correct Context on Listener Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "one" + 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" with parameter "fdc3.instrument" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" Then "{result}" is an object with the following contents | type | name | | fdc3.instrument | Apple | @@ -31,12 +31,12 @@ Feature: Updating User Channel State Scenario: Changing User Channel Doesn't Receive Incorrect Context on Listener Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "two" + 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" with parameter "fdc3.instrument" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" Then "{result}" is null Scenario: disconnection 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 index 367fcbad..c74bb6bc 100644 --- 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 @@ -16,7 +16,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel Scenario: Adding a Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -28,7 +28,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | 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 index 2e5fdca7..6727c16f 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature @@ -51,7 +51,7 @@ Feature: Basic User Channels Support Scenario: Changing Channel You should be able to join a channel knowing it's ID. - When I call "{api}" with "joinUserChannel" with parameter "one" + 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 | @@ -64,7 +64,7 @@ Feature: Basic User Channels Support Scenario: Changing Channel via Deprecated API You should be able to join a channel knowing it's ID. - When I call "{api}" with "joinChannel" with parameter "one" + 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 | @@ -76,8 +76,8 @@ Feature: Basic User Channels Support Scenario: Adding a Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -91,8 +91,8 @@ Feature: Basic User Channels Support Scenario: Adding an Un-Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "{empty}" and "{resultHandler}" + 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 | @@ -106,8 +106,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameter "{resultHandler}" + 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 | @@ -121,7 +121,7 @@ Feature: Basic User Channels Support Scenario: If you haven't joined a channel, your listener receives nothing Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -130,8 +130,8 @@ Feature: Basic User Channels Support Scenario: After unsubscribing, my listener shouldn't receive any more messages Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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" @@ -149,8 +149,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -164,24 +164,24 @@ Feature: Basic User Channels Support | id.ticker | type | name | Scenario: Joining a user channel that doesn't exist throws an error - When I call "{api}" with "joinUserChannel" with parameter "nonexistent" + 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" with parameters "{true}" and "{resultHandler}" + 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" with parameters "{null}" and "{true}" + 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" with parameter "one" + 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" with parameter "{instrumentContext}" + 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 | @@ -195,17 +195,17 @@ Feature: Basic User Channels Support 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" with parameter "one" + 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" with parameter "fdc3.email" + 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -216,7 +216,7 @@ Feature: Basic User Channels Support | 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" with parameter "one" + 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 @@ -224,7 +224,7 @@ Feature: Basic User Channels Support Scenario: Adding and removing A User Channel Changed Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "userChannelChanged" and "{typesHandler}" + 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}" @@ -244,7 +244,7 @@ Feature: Basic User Channels Support Scenario: Adding and removing A "null" (i.e. wildcard) Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "{null}" and "{typesHandler}" + 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}" @@ -264,57 +264,9 @@ Feature: Basic User Channels Support Scenario: Adding An Unknown Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "unknownEventType" and "{typesHandler}" + 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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" @@ -326,7 +278,7 @@ Feature: Basic User Channels Support Scenario: Destructured joinUserChannel and getCurrentChannel work correctly When I destructure method "joinUserChannel" from "{api}" - And I call destructured "joinUserChannel" with parameter "one" + 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 @@ -339,12 +291,12 @@ Feature: Basic User Channels Support Scenario: Destructured channel getCurrentContext after broadcast Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" + 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" with parameter "{instrumentContext}" + 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 | @@ -353,8 +305,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call destructured "broadcast" with parameter "{instrumentContext}" + 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" @@ -365,13 +317,13 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call destructured "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -382,8 +334,8 @@ Feature: Basic User Channels Support Scenario: User channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | From 0d4449cff789fe96f8b710ca2b355402af279f5c Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Thu, 21 May 2026 18:14:53 +0100 Subject: [PATCH 57/65] Committing some changes --- fdc3-agent-proxy/pom.xml | 65 ++++- .../finos/fdc3/proxy/DesktopAgentProxy.java | 37 ++- .../org/finos/fdc3/proxy/apps/AppSupport.java | 3 + .../org/finos/fdc3/proxy/RunCucumberTest.java | 43 +++ .../proxy/support/ParseAntiReplayClaims.java | 36 +++ .../fdc3/proxy/support/TestFieldMatchers.java | 160 +++++++++++ .../fdc3/proxy/support/TestMessaging.java | 19 +- .../java/org/finos/fdc3/api/DesktopAgent.java | 25 +- .../org/finos/fdc3/api/channel/Channel.java | 13 + .../fdc3/api/metadata/AntiReplayClaims.java | 64 +++++ .../fdc3/api/metadata/ContextMetadata.java | 255 +++++++++++++++++- ...DesktopAgentProvidableContextMetadata.java | 34 +++ .../fdc3/api/metadata/DetachedSignature.java | 53 ++++ .../fdc3/api/metadata/IntentResolution.java | 5 + .../fdc3/api/metadata/SecurityMetadata.java | 44 +++ .../fdc3/api/types/ContextWithMetadata.java | 53 ++++ .../org/finos/fdc3/api/types/FDC3Event.java | 4 +- .../finos/fdc3/api/types/IntentHandler.java | 5 +- 18 files changed, 894 insertions(+), 24 deletions(-) create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/RunCucumberTest.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/ParseAntiReplayClaims.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AntiReplayClaims.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DesktopAgentProvidableContextMetadata.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DetachedSignature.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/SecurityMetadata.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextWithMetadata.java diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index 51dcb4a8..afedf46f 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -19,6 +19,9 @@ 11 11 src/test/resources/temporary-features + 7.15.0 + 6.1.2 + @@ -36,7 +39,6 @@ ${project.version} - io.github.robmoffat standard-cucumber-steps @@ -44,6 +46,50 @@ 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 @@ -51,6 +97,19 @@ 2.0.9 + + com.github.erosb + everit-json-schema + 1.14.4 + test + + + org.json + json + 20240303 + test + + @@ -108,9 +167,7 @@ cucumber.junit-platform.naming-strategy=long - cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html - cucumber.glue=org.finos.fdc3.proxy,org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world,org.finos.fdc3.testing.steps - cucumber.features=classpath:features + 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 index e525adf5..d64f10fd 100644 --- 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 @@ -28,6 +28,7 @@ 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; @@ -80,10 +81,15 @@ public CompletionStage getInfo() { @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); + return channel.broadcast(context, metadata); } else { return CompletableFuture.completedFuture(null); } @@ -155,6 +161,18 @@ public CompletionStage raiseIntent(String intent, Context cont 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); @@ -165,11 +183,28 @@ public CompletionStage raiseIntentForContext(Context context, 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) { 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 index b70516d1..5ccecb08 100644 --- 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 @@ -21,6 +21,7 @@ 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; @@ -54,6 +55,8 @@ public interface AppSupport { */ CompletionStage open(AppIdentifier app, Context context); + CompletionStage open(AppIdentifier app, Context context, AppProvidableContextMetadata metadata); + /** * Open an application by name. * 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/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/TestFieldMatchers.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java new file mode 100644 index 00000000..c313235f --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java @@ -0,0 +1,160 @@ +/* + * 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.support; + +import static io.github.robmoffat.support.MatchingUtils.handleResolve; +import static io.github.robmoffat.support.MatchingUtils.pathForFieldSuffix; +import static io.github.robmoffat.support.MatchingUtils.registerFieldMatcher; + +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.JXPathNotFoundException; +import org.finos.fdc3.api.metadata.AntiReplayClaims; +import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.metadata.DetachedSignature; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.github.robmoffat.support.RowFieldMatcher; +import io.github.robmoffat.world.PropsWorld; + +/** + * Field matchers for FDC3-specific assertion columns in Cucumber tables. + */ +public final class TestFieldMatchers { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String SIGNATURE_SUFFIX = ".signature"; + private static final String IAT_SUFFIX = ".iat"; + + static { + registerFieldMatcher(signatureFieldMatcher()); + registerFieldMatcher(antiReplayIatMatcher()); + } + + private TestFieldMatchers() { + } + + private static RowFieldMatcher signatureFieldMatcher() { + return new RowFieldMatcher() { + @Override + public boolean matchesField(String field) { + return "signature".equals(field) || field.endsWith(SIGNATURE_SUFFIX); + } + + @Override + public boolean matchField(PropsWorld world, String field, String expected, Object rowData) { + String path = "signature".equals(field) ? "signature" : pathForFieldSuffix(field, SIGNATURE_SUFFIX); + if (path == null) { + return false; + } + Object found = valueAtPath(rowData, path); + String actual = signatureValue(found); + Object resolved = handleResolve(expected, world); + if (!Objects.equals(asString(actual), asString(resolved))) { + world.log(String.format("Signature match failed on %s: '%s' vs '%s'", field, actual, resolved)); + return false; + } + return true; + } + }; + } + + private static RowFieldMatcher antiReplayIatMatcher() { + return new RowFieldMatcher() { + @Override + public boolean matchesField(String field) { + return field.endsWith(IAT_SUFFIX); + } + + @Override + public boolean matchField(PropsWorld world, String field, String expected, Object rowData) { + String path = pathForFieldSuffix(field, IAT_SUFFIX); + if (path == null) { + return false; + } + Object found = valueAtPath(rowData, path); + Object resolved = handleResolve(expected, world); + if (!numericEquals(found, resolved)) { + world.log(String.format("Numeric match failed on %s: '%s' vs '%s'", field, found, resolved)); + return false; + } + return true; + } + }; + } + + @SuppressWarnings("unchecked") + public static Map metadataToAssertionMap(ContextMetadata metadata) { + if (metadata == null) { + return null; + } + return MAPPER.convertValue(metadata, Map.class); + } + + private static String signatureValue(Object found) { + if (found == null) { + return null; + } + if (found instanceof DetachedSignature) { + return ((DetachedSignature) found).getSignature(); + } + if (found instanceof Map) { + Object inner = ((Map) found).get("signature"); + return inner == null ? null : String.valueOf(inner); + } + return String.valueOf(found); + } + + private static boolean numericEquals(Object found, Object expected) { + if (found == null && expected == null) { + return true; + } + if (found == null || expected == null) { + return false; + } + if (found instanceof Number && expected instanceof Number) { + return ((Number) found).longValue() == ((Number) expected).longValue(); + } + String foundStr = String.valueOf(found); + String expectedStr = String.valueOf(expected); + if (Pattern.matches("-?\\d+", foundStr) && Pattern.matches("-?\\d+(\\.0)?", expectedStr)) { + return Long.parseLong(foundStr) == (long) Double.parseDouble(expectedStr); + } + return Objects.equals(foundStr, expectedStr); + } + + 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.replace('.', '/'); + return context.getValue(xpathName); + } catch (JXPathNotFoundException e) { + return null; + } + } + + private static String asString(Object value) { + return value == null ? null : String.valueOf(value); + } +} 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 index c80ad6a5..67448eb3 100644 --- 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 @@ -30,6 +30,7 @@ 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; @@ -68,9 +69,13 @@ public class TestMessaging extends AbstractMessaging { 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()); @@ -83,13 +88,12 @@ public TestMessaging(Map> channelState) { this.automaticResponses.add(new FindInstancesResponse()); this.automaticResponses.add(new OpenResponse()); this.automaticResponses.add(new GetOrCreateChannelResponse()); - this.automaticResponses.add(new ChannelStateResponse(this.channelState)); + 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()); - this.automaticResponses.add(new AddEventListenerResponse()); } @Override @@ -274,6 +278,7 @@ public void setResultType(String resultType) { public static class PossibleIntentResult { private Context context; private Channel channel; + private ContextMetadata resultMetadata; private String error; private boolean timeout; @@ -293,6 +298,14 @@ 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; } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java index 237dbb0f..a61205ed 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/DesktopAgent.java @@ -21,6 +21,7 @@ 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; @@ -81,6 +82,8 @@ public interface DesktopAgent { */ CompletionStage open(AppIdentifier app, Context context); + CompletionStage open(AppIdentifier app, Context context, AppProvidableContextMetadata metadata); + default CompletionStage open(AppIdentifier app) { return open(app, null); } @@ -330,6 +333,8 @@ default CompletionStage> findIntentsByContext(Context context) { */ CompletionStage broadcast(Context context); + CompletionStage broadcast(Context context, AppProvidableContextMetadata metadata); + /** * Raises a specific intent for resolution against apps registered with the * desktop agent. @@ -402,8 +407,16 @@ default CompletionStage> findIntentsByContext(Context context) { */ CompletionStage raiseIntent(String intent, Context context, AppIdentifier app); + CompletionStage raiseIntent( + String intent, Context context, AppIdentifier app, AppProvidableContextMetadata metadata); + default CompletionStage raiseIntent(String intent, Context context) { - return raiseIntent(intent, context, null); + return raiseIntent(intent, context, (AppIdentifier) null); + } + + default CompletionStage raiseIntent( + String intent, Context context, AppProvidableContextMetadata metadata) { + return raiseIntent(intent, context, (AppIdentifier) null, metadata); } /** @@ -447,8 +460,16 @@ default CompletionStage raiseIntent(String intent, Context con */ CompletionStage raiseIntentForContext(Context context, AppIdentifier app); + CompletionStage raiseIntentForContext( + Context context, AppIdentifier app, AppProvidableContextMetadata metadata); + default CompletionStage raiseIntentForContext(Context context) { - return raiseIntentForContext(context, null); + return raiseIntentForContext(context, (AppIdentifier) null); + } + + default CompletionStage raiseIntentForContext( + Context context, AppProvidableContextMetadata metadata) { + return raiseIntentForContext(context, (AppIdentifier) null, metadata); } /** diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java index b700d4ae..a709239d 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/channel/Channel.java @@ -20,8 +20,10 @@ import java.util.concurrent.CompletionStage; import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; import org.finos.fdc3.api.metadata.DisplayMetadata; import org.finos.fdc3.api.types.ContextHandler; +import org.finos.fdc3.api.types.ContextWithMetadata; import org.finos.fdc3.api.types.IntentResult; import org.finos.fdc3.api.types.Listener; @@ -79,6 +81,8 @@ public String toString() { */ CompletionStage broadcast(Context context); + CompletionStage broadcast(Context context, AppProvidableContextMetadata metadata); + /** * When a `contextType`_` is provided, the most recent context matching the type will be returned, or `null` if no matching context is found. * @@ -92,6 +96,15 @@ public String toString() { CompletionStage> getCurrentContext(String contextType); + /** + * Returns the most recent context on the channel along with its metadata, or empty if none. + */ + default CompletionStage> getCurrentContextWithMetadata() { + return getCurrentContextWithMetadata(null); + } + + CompletionStage> getCurrentContextWithMetadata(String contextType); + /** * Adds a listener for incoming contexts of the specified _context type_ whenever a broadcast happens on this channel. * diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AntiReplayClaims.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AntiReplayClaims.java new file mode 100644 index 00000000..ad81815e --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AntiReplayClaims.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.api.metadata; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AntiReplayClaims { + + private long iat; + private long exp; + private String jti; + + public AntiReplayClaims() { + } + + public AntiReplayClaims(long iat, long exp, String jti) { + this.iat = iat; + this.exp = exp; + this.jti = jti; + } + + @JsonProperty("iat") + public long getIat() { + return iat; + } + + public void setIat(long iat) { + this.iat = iat; + } + + @JsonProperty("exp") + public long getExp() { + return exp; + } + + public void setExp(long exp) { + this.exp = exp; + } + + @JsonProperty("jti") + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } +} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index 8c7c24eb..7d57dbf6 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -1,5 +1,5 @@ -/** - * Copyright FINOS and its Contributors +/* + * 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. @@ -13,30 +13,261 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.finos.fdc3.api.metadata; -import java.util.HashMap; +import java.time.Instant; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; import org.finos.fdc3.api.types.AppIdentifier; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + /** - * Metadata introduced in FDC3 2.0 - * + * Map-backed metadata for context and intent messages. + *

+ * Implements {@link AppProvidableContextMetadata} for outbound app-provided fields and + * {@link DesktopAgentProvidableContextMetadata} for optional Desktop Agent fields. + * When received via listeners, {@link #getSource()} and {@link #getTimestamp()} are + * typically populated by the Desktop Agent. */ -public class ContextMetadata extends HashMap { +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ContextMetadata implements AppProvidableContextMetadata, DesktopAgentProvidableContextMetadata { + + private final Map data; public ContextMetadata() { + this.data = new LinkedHashMap<>(); + } + + public ContextMetadata(Map initial) { + this.data = initial != null ? new LinkedHashMap<>(initial) : new LinkedHashMap<>(); + } + + /** Returns a new empty instance suitable for outbound app-provided metadata. */ + public static ContextMetadata appProvidable() { + return new ContextMetadata(); + } + + /** Returns an unmodifiable view of the backing map. */ + @JsonIgnore + public Map asMap() { + return Collections.unmodifiableMap(data); + } + + /** Returns a mutable copy of the backing map (for wire serialization). */ + @JsonIgnore + public Map toMap() { + return new LinkedHashMap<>(data); + } + + @JsonAnyGetter + public Map jsonProperties() { + return data; + } + + @JsonAnySetter + public void jsonProperty(String key, Object value) { + data.put(key, value); + } + + @Override + @JsonProperty("traceId") + public String getTraceId() { + return (String) data.get("traceId"); + } + + @Override + public void setTraceId(String traceId) { + putOrRemove("traceId", traceId); + } + + @Override + @JsonProperty("signature") + public DetachedSignature getSignature() { + return getTyped("signature", DetachedSignature.class); + } + + @Override + public void setSignature(DetachedSignature signature) { + putOrRemove("signature", signature); + } + + @Override + @JsonProperty("antiReplay") + public AntiReplayClaims getAntiReplay() { + return getTyped("antiReplay", AntiReplayClaims.class); + } + + @Override + public void setAntiReplay(AntiReplayClaims antiReplay) { + putOrRemove("antiReplay", antiReplay); + } + + @Override + @JsonProperty("authenticity") + public String getAuthenticity() { + return (String) data.get("authenticity"); + } + + @Override + public void setAuthenticity(String authenticity) { + putOrRemove("authenticity", authenticity); + } + + @Override + @JsonProperty("encryption") + public String getEncryption() { + return (String) data.get("encryption"); + } + + @Override + public void setEncryption(String encryption) { + putOrRemove("encryption", encryption); } + @Override + @SuppressWarnings("unchecked") + @JsonProperty("custom") + public Map getCustom() { + Object custom = data.get("custom"); + if (custom instanceof Map) { + return (Map) custom; + } + return null; + } + @Override + public void setCustom(Map custom) { + putOrRemove("custom", custom); + } + + @Override + @JsonProperty("timestamp") + public Instant getTimestamp() { + Object value = data.get("timestamp"); + if (value instanceof Instant) { + return (Instant) value; + } + if (value instanceof String) { + return Instant.parse((String) value); + } + return null; + } + + @Override + public void setTimestamp(Instant timestamp) { + putOrRemove("timestamp", timestamp); + } + + @Override + @JsonProperty("source") public AppIdentifier getSource() { - AppIdentifier source = (AppIdentifier) this.get("source"); - return source; + Object source = data.get("source"); + if (source instanceof AppIdentifier) { + return (AppIdentifier) source; + } + if (source instanceof Map) { + return AppIdentifier.fromMap((Map) source); + } + return null; } - + + @Override public void setSource(AppIdentifier source) { - this.put("source", source); + putOrRemove("source", source); + } + + public static ContextMetadata fromMap(Map map) { + if (map == null) { + return null; + } + ContextMetadata metadata = new ContextMetadata(); + metadata.mergeFrom(map); + return metadata; + } + + public void mergeFrom(Map map) { + if (map == null) { + return; + } + Object source = map.get("source"); + if (source != null) { + setSource(source instanceof AppIdentifier ? (AppIdentifier) source : AppIdentifier.fromMap(castMap(source))); + } + Object timestamp = map.get("timestamp"); + if (timestamp instanceof Instant) { + setTimestamp((Instant) timestamp); + } else if (timestamp instanceof String) { + setTimestamp(Instant.parse((String) timestamp)); + } + setTraceId((String) map.get("traceId")); + Object signature = map.get("signature"); + if (signature instanceof DetachedSignature) { + setSignature((DetachedSignature) signature); + } else if (signature instanceof Map) { + setSignature(mapToDetachedSignature(castMap(signature))); + } + Object antiReplay = map.get("antiReplay"); + if (antiReplay instanceof AntiReplayClaims) { + setAntiReplay((AntiReplayClaims) antiReplay); + } else if (antiReplay instanceof Map) { + setAntiReplay(mapToAntiReplay(castMap(antiReplay))); + } + Object custom = map.get("custom"); + if (custom instanceof Map) { + setCustom(castMap(custom)); + } + setAuthenticity((String) map.get("authenticity")); + setEncryption((String) map.get("encryption")); + } + + private void putOrRemove(String key, Object value) { + if (value == null) { + data.remove(key); + } else { + data.put(key, value); + } + } + + private T getTyped(String key, Class type) { + Object value = data.get(key); + if (type.isInstance(value)) { + return type.cast(value); + } + if (value instanceof Map) { + if (type == DetachedSignature.class) { + return type.cast(mapToDetachedSignature(castMap(value))); + } + if (type == AntiReplayClaims.class) { + return type.cast(mapToAntiReplay(castMap(value))); + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static Map castMap(Object value) { + return (Map) value; + } + + private static DetachedSignature mapToDetachedSignature(Map map) { + DetachedSignature sig = new DetachedSignature(); + sig.setProtectedHeader((String) map.get("protected")); + sig.setSignature((String) map.get("signature")); + return sig; + } + + private static AntiReplayClaims mapToAntiReplay(Map map) { + Object iat = map.get("iat"); + Object exp = map.get("exp"); + long iatVal = iat instanceof Number ? ((Number) iat).longValue() : Long.parseLong(String.valueOf(iat)); + long expVal = exp instanceof Number ? ((Number) exp).longValue() : Long.parseLong(String.valueOf(exp)); + return new AntiReplayClaims(iatVal, expVal, (String) map.get("jti")); } - } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DesktopAgentProvidableContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DesktopAgentProvidableContextMetadata.java new file mode 100644 index 00000000..47b8bca6 --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DesktopAgentProvidableContextMetadata.java @@ -0,0 +1,34 @@ +/* + * 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.api.metadata; + +import java.time.Instant; + +import org.finos.fdc3.api.types.AppIdentifier; + +/** + * Metadata fields that may be provided by the Desktop Agent (optional source and timestamp). + */ +public interface DesktopAgentProvidableContextMetadata extends SecurityMetadata { + + Instant getTimestamp(); + + void setTimestamp(Instant timestamp); + + AppIdentifier getSource(); + + void setSource(AppIdentifier source); +} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DetachedSignature.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DetachedSignature.java new file mode 100644 index 00000000..5287e051 --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/DetachedSignature.java @@ -0,0 +1,53 @@ +/** + * 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.api.metadata; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DetachedSignature { + + private String protectedHeader; + private String signature; + + public DetachedSignature() { + } + + public DetachedSignature(String protectedHeader, String signature) { + this.protectedHeader = protectedHeader; + this.signature = signature; + } + + @JsonProperty("protected") + public String getProtectedHeader() { + return protectedHeader; + } + + public void setProtectedHeader(String protectedHeader) { + this.protectedHeader = protectedHeader; + } + + @JsonProperty("signature") + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } +} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java index 27b74d87..8f777abb 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/IntentResolution.java @@ -82,4 +82,9 @@ public interface IntentResolution { * object of an invalid type. */ CompletionStage getResult(); + + /** + * Retrieves metadata about the intent result from the resolving application. + */ + CompletionStage getResultMetadata(); } diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/SecurityMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/SecurityMetadata.java new file mode 100644 index 00000000..8ee73127 --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/SecurityMetadata.java @@ -0,0 +1,44 @@ +/* + * 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.api.metadata; + +/** + * Security-related metadata fields shared across context and intent metadata types. + */ +public interface SecurityMetadata { + + DetachedSignature getSignature(); + + void setSignature(DetachedSignature signature); + + AntiReplayClaims getAntiReplay(); + + void setAntiReplay(AntiReplayClaims antiReplay); + + /** + * Result of signature verification by the receiving app's security layer. + */ + String getAuthenticity(); + + void setAuthenticity(String authenticity); + + /** + * Result of decryption by the receiving app's security layer. + */ + String getEncryption(); + + void setEncryption(String encryption); +} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextWithMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextWithMetadata.java new file mode 100644 index 00000000..ddc9165e --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/ContextWithMetadata.java @@ -0,0 +1,53 @@ +/** + * 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.api.types; + +import org.finos.fdc3.api.context.Context; +import org.finos.fdc3.api.metadata.ContextMetadata; + +/** + * Context object paired with its associated metadata. + */ +public class ContextWithMetadata { + + private Context context; + private ContextMetadata metadata; + + public ContextWithMetadata() { + } + + public ContextWithMetadata(Context context, ContextMetadata metadata) { + this.context = context; + this.metadata = metadata; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + + public ContextMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ContextMetadata metadata) { + this.metadata = metadata; + } +} diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java index e708636b..f25f455e 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java @@ -35,7 +35,9 @@ public enum Type { ADD_CONTEXT_LISTENER("addContextListener"), ON_UNSUBSCRIBE("onUnsubscribe"), ON_DISCONNECT("onDisconnect"), - USER_CHANNEL_CHANGED("userChannelChanged"); + USER_CHANNEL_CHANGED("userChannelChanged"), + CONTEXT_CLEARED("contextCleared"); + private final String value; diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java index 17fa6cc4..9b5b185f 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/IntentHandler.java @@ -37,6 +37,9 @@ public interface IntentHandler { * @return A {@link CompletionStage} that will be used to publish the * intent result */ - CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata); + /** + * @return context, channel, {@link org.finos.fdc3.api.types.ContextWithMetadata}, or empty for void + */ + CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata); } From d4c3afa96c91433efbadcf5d8927f3aa041369ac Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Thu, 21 May 2026 18:41:36 +0100 Subject: [PATCH 58/65] pushing code - 60+ tests working --- fdc3-agent-proxy/pom.xml | 15 +- .../fdc3/proxy/apps/DefaultAppSupport.java | 16 +- .../fdc3/proxy/channels/DefaultChannel.java | 54 +++++ .../proxy/channels/DefaultChannelSupport.java | 52 +++-- .../DefaultUserChannelContextListener.java | 6 +- .../intents/DefaultIntentResolution.java | 98 +-------- .../proxy/intents/DefaultIntentSupport.java | 165 +++++++++++--- .../fdc3/proxy/intents/IntentSupport.java | 25 ++- .../listeners/DefaultContextListener.java | 15 +- .../listeners/DefaultIntentListener.java | 75 +++++-- .../proxy/util/ContextMetadataMapper.java | 59 +++++ .../fdc3/proxy/schema/Fdc3SchemaMatchers.java | 114 ++++++++++ .../finos/fdc3/proxy/schema/LoadSchemas.java | 203 ++++++++++++++++++ .../finos/fdc3/proxy/steps/AgentSteps.java | 14 +- .../finos/fdc3/proxy/steps/IntentSteps.java | 56 ++++- .../finos/fdc3/proxy/steps/SchemaSteps.java | 49 +++++ .../fdc3/proxy/support/TestFieldMatchers.java | 6 +- .../responses/ChannelStateResponse.java | 21 +- .../responses/RaiseIntentResponse.java | 6 +- .../AppProvidableContextMetadata.java | 33 +++ 20 files changed, 894 insertions(+), 188 deletions(-) create mode 100644 fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/Fdc3SchemaMatchers.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/LoadSchemas.java create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/SchemaSteps.java create mode 100644 fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppProvidableContextMetadata.java diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index afedf46f..a81e6a49 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -13,7 +13,7 @@ fdc3-agent-proxy FDC3 Desktop Agent Proxy Desktop Agent Proxy implementation for FDC3 - + UTF-8 11 @@ -21,6 +21,7 @@ src/test/resources/temporary-features 7.15.0 6.1.2 + 1.5.6 @@ -98,15 +99,9 @@ - com.github.erosb - everit-json-schema - 1.14.4 - test - - - org.json - json - 20240303 + com.networknt + json-schema-validator + ${networknt.version} test 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 index 9c5134a2..b9362790 100644 --- 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 @@ -27,8 +27,10 @@ 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.*; @@ -110,11 +112,15 @@ public CompletionStage getAppMetadata(AppIdentifier app) { @Override public CompletionStage open(AppIdentifier app, Context context) { - // Build typed request + 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) { @@ -122,8 +128,12 @@ public CompletionStage open(AppIdentifier app, Context context) { } request.setPayload(payload); - // Convert to Map for messaging 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 -> { 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 index e83e438a..1c44a634 100644 --- 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 @@ -25,10 +25,15 @@ 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.*; @@ -83,6 +88,12 @@ public DisplayMetadata getDisplayMetadata() { @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()); @@ -93,6 +104,11 @@ public CompletionStage broadcast(Context 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); @@ -131,6 +147,44 @@ public CompletionStage> getCurrentContext(String contextType) }); } + @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 -> { + GetCurrentContextResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentContextResponse.class); + + if (typedResponse.getPayload() == null + || typedResponse.getPayload().getContext() == null) { + return Optional.empty(); + } + + Context context = typedResponse.getPayload().getContext(); + Map responseMap = response; + Map responsePayload = (Map) responseMap.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) { 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 index f34484a3..3806f232 100644 --- 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 @@ -31,6 +31,7 @@ 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; @@ -60,7 +61,7 @@ /** * Default implementation of ChannelSupport. */ -public class DefaultChannelSupport implements ChannelSupport { +public class DefaultChannelSupport implements ChannelSupport, Connectable { private final Messaging messaging; private final ChannelSelector channelSelector; @@ -68,6 +69,7 @@ public class DefaultChannelSupport implements ChannelSupport { 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; @@ -83,18 +85,31 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto 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()); + } - // Listen for channel changed events from the Desktop Agent - addEventListener(event -> { + 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); - getUserChannelsCached().thenCompose(channels -> { + getUserChannelsCached().thenAccept(channels -> { Channel theChannel = null; - // If there's a newChannelId, retrieve details of the channel if (newChannelId != null) { theChannel = channels.stream() .filter(c -> newChannelId.equals(c.getId())) @@ -102,10 +117,10 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto .orElse(null); if (theChannel == null) { - // Channel not found - query user channels in case they have changed - Logger.debug("Unknown user channel, querying Desktop Agent for updated user channels: {}", + Logger.debug( + "Unknown user channel, querying Desktop Agent for updated user channels: {}", newChannelId); - return getUserChannels().thenApply(updatedChannels -> { + getUserChannels().thenAccept(updatedChannels -> { Channel foundChannel = updatedChannels.stream() .filter(c -> newChannelId.equals(c.getId())) .findFirst() @@ -118,19 +133,28 @@ public DefaultChannelSupport(Messaging messaging, ChannelSelector channelSelecto } currentChannel = foundChannel; - channelSelector.updateChannel(foundChannel != null ? foundChannel.getId() : null, - updatedChannels); - return null; + channelSelector.updateChannel( + foundChannel != null ? foundChannel.getId() : null, updatedChannels); + for (UserChannelContextListener listener : userChannelListeners) { + listener.changeChannel(); + } }); + return; } } - // Channel found in cache or newChannelId is null currentChannel = theChannel; channelSelector.updateChannel(theChannel != null ? theChannel.getId() : null, channels); - return CompletableFuture.completedFuture(null); + for (UserChannelContextListener listener : userChannelListeners) { + listener.changeChannel(); + } }); - }, "userChannelChanged"); + }, "userChannelChanged").thenApply(listener -> null); + } + + @Override + public CompletionStage disconnect() { + return CompletableFuture.completedFuture(null); } @Override 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 index 795ae766..3c095681 100644 --- 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 @@ -60,9 +60,9 @@ public CompletionStage register() { public void changeChannel() { Channel currentChannel = channelSupport.getCurrentChannelInternal(); if (currentChannel != null) { - currentChannel.getCurrentContext(contextType) - .thenAccept(contextOpt -> { - contextOpt.ifPresent(context -> handler.handleContext(context, null)); + currentChannel.getCurrentContextWithMetadata(contextType) + .thenAccept(resultOpt -> { + resultOpt.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); }); } } 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 index ec843957..708d3830 100644 --- 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 @@ -16,19 +16,14 @@ package org.finos.fdc3.proxy.intents; -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.DisplayMetadata; +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; -import org.finos.fdc3.proxy.channels.DefaultChannel; -import org.finos.fdc3.proxy.channels.DefaultPrivateChannel; /** * Default implementation of IntentResolution. @@ -37,19 +32,22 @@ public class DefaultIntentResolution implements IntentResolution { private final Messaging messaging; private final long messageExchangeTimeout; - private final CompletionStage resultPromise; + 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 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; } @@ -70,86 +68,12 @@ public Optional getVersion() { } @Override - @SuppressWarnings("unchecked") public CompletionStage getResult() { - return resultPromise.thenApply(result -> { - // Return null when there's no result (void) - if (result == null) { - return (IntentResult) null; - } - - // The result from the Desktop Agent is a map with either 'context' or 'channel' key - if (result instanceof Map) { - Map resultMap = (Map) result; - - // Empty map means void result - if (resultMap.isEmpty()) { - return (IntentResult) null; - } - - // Check for context result - Object contextObj = resultMap.get("context"); - if (contextObj != null) { - if (contextObj instanceof Context) { - return (IntentResult) contextObj; - } else if (contextObj instanceof Map) { - return (IntentResult) Context.fromMap((Map) contextObj); - } - } - - // Check for channel result - Object channelObj = resultMap.get("channel"); - if (channelObj != null && channelObj instanceof Map) { - Map channelMap = (Map) channelObj; - return (IntentResult) createChannel(channelMap); - } - } - - // If result is already a Context or Channel, return it directly - if (result instanceof IntentResult) { - return (IntentResult) result; - } - - return (IntentResult) null; - }); + return resultPromise; } - - @SuppressWarnings("unchecked") - private Channel createChannel(Map channelMap) { - String id = (String) channelMap.get("id"); - Object typeObj = channelMap.get("type"); - - Channel.Type type; - if (typeObj instanceof Channel.Type) { - type = (Channel.Type) typeObj; - } else { - String typeStr = typeObj != null ? typeObj.toString() : null; - if ("user".equals(typeStr)) { - type = Channel.Type.User; - } else if ("app".equals(typeStr)) { - type = Channel.Type.App; - } else if ("private".equals(typeStr)) { - type = Channel.Type.Private; - } else { - type = Channel.Type.App; // default - } - } - - Map displayMetadataMap = (Map) channelMap.get("displayMetadata"); - DisplayMetadata displayMetadata = null; - if (displayMetadataMap != null) { - displayMetadata = new DisplayMetadata( - (String) displayMetadataMap.get("name"), - (String) displayMetadataMap.get("color"), - (String) displayMetadataMap.get("glyph") - ); - } - - // Use existing channel implementations - if (type == Channel.Type.Private) { - return new DefaultPrivateChannel(messaging, messageExchangeTimeout, id); - } else { - return new DefaultChannel(messaging, messageExchangeTimeout, id, type, displayMetadata); - } + + @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 index 2bb5ffa0..2bcd0f74 100644 --- 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 @@ -19,19 +19,27 @@ 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.*; /** @@ -123,8 +131,13 @@ public CompletionStage> findIntentsByContext(Context context) { @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(); - String requestUuid = meta.getRequestUUID(); RaiseIntentRequest request = new RaiseIntentRequest(); request.setType(RaiseIntentRequestType.RAISE_INTENT_REQUEST); @@ -139,8 +152,11 @@ public CompletionStage raiseIntent(String intent, Context cont request.setPayload(payload); Map requestMap = messaging.getConverter().toMap(request); - - CompletionStage resultPromise = createResultPromise(requestUuid); + @SuppressWarnings("unchecked") + Map payloadMap = (Map) requestMap.get("payload"); + if (payloadMap != null) { + payloadMap.put("metadata", ContextMetadataMapper.toWire(metadata)); + } return messaging.>exchange(requestMap, "raiseIntentResponse", appLaunchTimeout) .thenCompose(response -> { @@ -152,7 +168,8 @@ public CompletionStage raiseIntent(String intent, Context cont } AppIntent schemaAppIntent = typedResponse.getPayload().getAppIntent(); - org.finos.fdc3.schema.IntentResolution schemaIntentResolution = typedResponse.getPayload().getIntentResolution(); + org.finos.fdc3.schema.IntentResolution schemaIntentResolution = + typedResponse.getPayload().getIntentResolution(); if (schemaAppIntent == null && schemaIntentResolution == null) { throw new RuntimeException(ResolveError.NoAppsFound.toString()); @@ -164,22 +181,32 @@ public CompletionStage raiseIntent(String intent, Context cont if (choice == null) { throw new RuntimeException(ResolveError.UserCancelled.toString()); } - return raiseIntent(intent, context, choice.getAppId()); + return raiseIntent(intent, context, choice.getAppId(), metadata); }); - } else { - AppIdentifier source = schemaIntentResolution.getSource(); - String resolvedIntent = schemaIntentResolution.getIntent(); - - return java.util.concurrent.CompletableFuture.completedFuture( - new DefaultIntentResolution(messaging, messageExchangeTimeout, resultPromise, source, resolvedIntent)); } + + 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(); - String requestUuid = meta.getRequestUUID(); RaiseIntentForContextRequest request = new RaiseIntentForContextRequest(); request.setType(RaiseIntentForContextRequestType.RAISE_INTENT_FOR_CONTEXT_REQUEST); @@ -193,8 +220,11 @@ public CompletionStage raiseIntentForContext(Context context, request.setPayload(payload); Map requestMap = messaging.getConverter().toMap(request); - - CompletionStage resultPromise = createResultPromise(requestUuid); + @SuppressWarnings("unchecked") + Map raiseForContextPayload = (Map) requestMap.get("payload"); + if (raiseForContextPayload != null) { + raiseForContextPayload.put("metadata", ContextMetadataMapper.toWire(metadata)); + } return messaging.>exchange(requestMap, "raiseIntentForContextResponse", appLaunchTimeout) .thenCompose(response -> { @@ -206,29 +236,33 @@ public CompletionStage raiseIntentForContext(Context context, } AppIntent[] schemaAppIntents = typedResponse.getPayload().getAppIntents(); - org.finos.fdc3.schema.IntentResolution schemaIntentResolution = typedResponse.getPayload().getIntentResolution(); + 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) { - List appIntents = Arrays.asList(schemaAppIntents); - - return intentResolver.chooseIntent(appIntents, context) + 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()); + return raiseIntent(choice.getIntent(), context, choice.getAppId(), metadata); }); - } else { - AppIdentifier source = schemaIntentResolution.getSource(); - String resolvedIntent = schemaIntentResolution.getIntent(); - - return java.util.concurrent.CompletableFuture.completedFuture( - new DefaultIntentResolution(messaging, messageExchangeTimeout, resultPromise, source, resolvedIntent)); } + + 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)); }); } @@ -238,12 +272,20 @@ public CompletionStage addIntentListener(String intent, IntentHandler return listener.register().thenApply(v -> listener); } - // ============ Helper methods ============ - // Schema now uses fdc3-standard AppIdentifier directly, no conversion needed + 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 CompletionStage createResultPromise(String requestUuid) { - return messaging.>waitFor( + 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"); @@ -253,8 +295,71 @@ private CompletionStage createResultPromise(String requestUuid) { 0, null ).thenApply(response -> { + metadataFuture.complete(extractResultMetadata(response, source)); Map payload = (Map) response.get("payload"); - return payload.get("intentResult"); + 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 index d8db01cf..23a8b2c7 100644 --- 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 @@ -21,6 +21,7 @@ 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; @@ -59,6 +60,18 @@ public interface IntentSupport { */ 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. * @@ -68,6 +81,17 @@ public interface IntentSupport { */ 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. * @@ -77,4 +101,3 @@ public interface IntentSupport { */ CompletionStage addIntentListener(String intent, IntentHandler handler); } - 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 index 1c22f929..6c26cf95 100644 --- 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 @@ -21,8 +21,10 @@ 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. @@ -76,11 +78,9 @@ public void changeChannel(Channel channel) { } else { this.channelId = channel.getId(); // Get current context from the channel - channel.getCurrentContext(contextType) - .thenAccept(context -> { - if (context.isPresent()) { - handler.handleContext(context.get(), null); - } + channel.getCurrentContextWithMetadata(contextType) + .thenAccept(result -> { + result.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); }); } } @@ -128,6 +128,9 @@ public void action(Map message) { Map payload = (Map) message.get("payload"); Map contextMap = (Map) payload.get("context"); Context context = Context.fromMap(contextMap); - handler.handleContext(context, null); + Map payloadMetadata = (Map) payload.get("metadata"); + Object messageTimestamp = ((Map) message.get("meta")).get("timestamp"); + ContextMetadata metadata = ContextMetadataMapper.fromWire(payloadMetadata, messageTimestamp); + 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 index 49d1342e..cfb000c9 100644 --- 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 @@ -24,12 +24,14 @@ 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.AppIdentifier; +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.IntentResult; import org.finos.fdc3.schema.IntentResultRequest; import org.finos.fdc3.schema.IntentResultRequestPayload; import org.finos.fdc3.schema.IntentResultRequestType; @@ -93,28 +95,37 @@ public void action(Map message) { IntentEvent intentEvent = messaging.getConverter().convertValue(message, IntentEvent.class); Context context = intentEvent.getPayload().getContext(); - AppIdentifier originatingApp = intentEvent.getPayload().getOriginatingApp(); - - ContextMetadata contextMetadata = new ContextMetadata(); - contextMetadata.setSource(originatingApp); - - // Call the handler and get the result - CompletionStage> resultFuture = - handler.handleIntent(context, contextMetadata); - - // Handle the intent result + 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, + CompletionStage> resultFuture, IntentEvent intentEvent) { - + resultFuture.thenAccept(optionalResult -> { - IntentResultRequest request = createIntentResultRequest(optionalResult.orElse(null), intentEvent); - - // Convert to Map and send + 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, @@ -127,7 +138,7 @@ private void handleIntentResult( }); }).exceptionally(ex -> { // Handler threw an exception, send empty result - IntentResultRequest request = createIntentResultRequest(null, intentEvent); + IntentResultRequest request = createIntentResultRequest(null, null, intentEvent); Map requestMap = messaging.getConverter().toMap(request); messaging.>exchange( @@ -142,8 +153,30 @@ private void handleIntentResult( }); } + 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( - org.finos.fdc3.api.types.IntentResult apiResult, + IntentResult apiResult, + AppProvidableContextMetadata appMetadata, IntentEvent intentEvent) { IntentResultRequest request = new IntentResultRequest(); @@ -164,8 +197,8 @@ private IntentResultRequest createIntentResultRequest( return request; } - private IntentResult convertIntentResult(org.finos.fdc3.api.types.IntentResult apiResult) { - IntentResult schemaResult = new IntentResult(); + 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 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..f7f52a3a --- /dev/null +++ b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java @@ -0,0 +1,59 @@ +/** + * 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 org.finos.fdc3.api.metadata.AppProvidableContextMetadata; +import org.finos.fdc3.api.metadata.ContextMetadata; + +/** + * 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 metadata == null ? new LinkedHashMap<>() : ((ContextMetadata) metadata).toMap(); + } + + public static ContextMetadata fromWire(Map payloadMetadata, Object messageTimestamp) { + 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()); + } + return metadata; + } +} 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..f9be3f6e --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/schema/LoadSchemas.java @@ -0,0 +1,203 @@ +/** + * 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 (npm) 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/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/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 index 515fd0f1..c4e498ed 100644 --- 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 @@ -49,10 +49,19 @@ public AgentSteps(CustomWorld 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<>())); + world.setMessaging(new TestMessaging(channelState != null ? channelState : new HashMap<>(), initialChannelId)); } TestMessaging messaging = world.getMessaging(); @@ -65,7 +74,8 @@ public void aDesktopAgentIn(String field) throws Exception { List connectables = new ArrayList<>(); connectables.add(hs); - + connectables.add(cs); + DesktopAgentProxy da = new DesktopAgentProxy(hs, cs, is, as, connectables); da.connect().toCompletableFuture().get(); 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 index 4d198ef8..1aee1181 100644 --- 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 @@ -27,8 +27,11 @@ 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.api.metadata.DisplayMetadata; +import org.finos.fdc3.proxy.support.ParseAntiReplayClaims; import org.finos.fdc3.api.types.AppIdentifier; import org.finos.fdc3.api.types.ContextHandler; import org.finos.fdc3.api.types.IntentHandler; @@ -130,6 +133,40 @@ public void raiseIntentReturnsContext(String result) { world.getMessaging().setIntentResult(intentResult); } + @Given("Raise Intent returns a context of {string} with traceId {string} and signature {string} and antiReplay claims {string}") + public void raiseIntentReturnsContextWithMetadata( + String result, String traceId, String signature, String antiReplayClaims) { + TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); + intentResult.setContext((Context) handleResolve(result, world)); + ContextMetadata resultMetadata = new ContextMetadata(); + resultMetadata.setSource(new AppIdentifier("some-app", "abc123")); + resultMetadata.setTimestamp(Instant.parse("2024-01-01T00:00:00Z")); + resultMetadata.setTraceId(traceId); + resultMetadata.setSignature(new DetachedSignature( + signature + " (protected part)", + signature + " (signature part)")); + resultMetadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); + Map custom = new HashMap<>(); + custom.put("priority", "high"); + resultMetadata.setCustom(custom); + intentResult.setResultMetadata(resultMetadata); + world.getMessaging().setIntentResult(intentResult); + } + + @Given("{string} is metadata with traceId {string} and signature {string} and antiReplay claims {string}") + public void isMetadataWithTraceId(String field, String traceId, String signature, String antiReplayClaims) { + AppProvidableContextMetadata metadata = ContextMetadata.appProvidable(); + metadata.setTraceId(traceId); + metadata.setSignature(new DetachedSignature( + signature + " (protected part)", + signature + " (signature part)")); + metadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); + Map custom = new HashMap<>(); + custom.put("priority", "high"); + metadata.setCustom(custom); + world.set(field, metadata); + } + @Given("Raise Intent will throw a {string} error") public void raiseIntentWillThrowError(String error) { TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); @@ -217,7 +254,7 @@ public void pipesIntentTo(String intentHandlerName, String field) { IntentHandler ih = new IntentHandler() { @Override - public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { Map item = new HashMap<>(); item.put("context", context); item.put("metadata", contextMetadata); @@ -234,7 +271,7 @@ public void returnsAContextItem(String intentHandlerName) { IntentHandler ih = new IntentHandler() { @Override - public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { Map id = new HashMap<>(); id.put("in", "one"); id.put("out", "two"); @@ -250,7 +287,7 @@ public void returnsAChannel(String intentHandlerName) { IntentHandler ih = new IntentHandler() { @Override - public CompletionStage> handleIntent(Context context, + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { DisplayMetadata dm = new DisplayMetadata("Some Channel", "ochre","b;"); Channel c = new Channel() { @@ -294,6 +331,17 @@ public CompletionStage addContextListener(String contextType, ContextH 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)); @@ -308,7 +356,7 @@ public void returnsAVoidPromise(String intentHandlerName) { IntentHandler ih = new IntentHandler() { @Override - public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { + public CompletionStage> handleIntent(Context context, ContextMetadata contextMetadata) { return CompletableFuture.completedFuture(Optional.ofNullable(null)); } }; 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..a18908d7 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/SchemaSteps.java @@ -0,0 +1,49 @@ +/** + * 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(); + org.finos.fdc3.proxy.support.TestFieldMatchers.class.getName(); + } + + 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/support/TestFieldMatchers.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java index c313235f..2a15bb6c 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java @@ -29,8 +29,6 @@ import org.finos.fdc3.api.metadata.ContextMetadata; import org.finos.fdc3.api.metadata.DetachedSignature; -import com.fasterxml.jackson.databind.ObjectMapper; - import io.github.robmoffat.support.RowFieldMatcher; import io.github.robmoffat.world.PropsWorld; @@ -39,7 +37,6 @@ */ public final class TestFieldMatchers { - private static final ObjectMapper MAPPER = new ObjectMapper(); private static final String SIGNATURE_SUFFIX = ".signature"; private static final String IAT_SUFFIX = ".iat"; @@ -100,12 +97,11 @@ public boolean matchField(PropsWorld world, String field, String expected, Objec }; } - @SuppressWarnings("unchecked") public static Map metadataToAssertionMap(ContextMetadata metadata) { if (metadata == null) { return null; } - return MAPPER.convertValue(metadata, Map.class); + return metadata.toMap(); } private static String signatureValue(Object found) { 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 index 1702d1e7..384d14e2 100644 --- 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 @@ -30,7 +30,12 @@ public class ChannelStateResponse implements AutomaticResponse { 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 @@ -227,7 +232,21 @@ private Map createGetContextResponse(Map message 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"); + metadata.put("signature", "test-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)); 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 index 9ad2e6bd..616f4c93 100644 --- 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 @@ -206,7 +206,11 @@ private Map createRaiseIntentResultResponse(Map 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)); diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppProvidableContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppProvidableContextMetadata.java new file mode 100644 index 00000000..156ee5fc --- /dev/null +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/AppProvidableContextMetadata.java @@ -0,0 +1,33 @@ +/* + * 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.api.metadata; + +import java.util.Map; + +/** + * Metadata that may be provided by an app when calling broadcast, open, or raiseIntent, + * to be passed on to receiving apps. + */ +public interface AppProvidableContextMetadata extends SecurityMetadata { + + String getTraceId(); + + void setTraceId(String traceId); + + Map getCustom(); + + void setCustom(Map custom); +} From e6318c647265ee6f75517db044956bd4a3ea174b Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 5 Jun 2026 10:14:10 +0100 Subject: [PATCH 59/65] Added the schemas from 3.0.0 into temp-schemas --- .../listeners/DefaultContextListener.java | 5 +- .../proxy/util/ContextMetadataMapper.java | 34 +- .../finos/fdc3/proxy/steps/ChannelSteps.java | 125 ++-- .../finos/fdc3/proxy/steps/SchemaSteps.java | 1 - .../fdc3/proxy/support/TestFieldMatchers.java | 156 ----- .../fdc3/context/ContextRoundTripTest.java | 16 +- fdc3-schema/scripts/prepare-schemas.sh | 17 + .../src/main/schemas-temp/3.0.0/api/README.md | 29 + .../3.0.0/api/WCP1Hello.schema.json | 66 ++ .../3.0.0/api/WCP2LoadUrl.schema.json | 51 ++ .../3.0.0/api/WCP3Handshake.schema.json | 90 +++ .../api/WCP4ValidateAppIdentity.schema.json | 67 ++ ...idateAppIdentityFailedResponse.schema.json | 46 ++ ...CP5ValidateAppIdentityResponse.schema.json | 68 ++ .../3.0.0/api/WCP6Goodbye.schema.json | 31 + .../3.0.0/api/WCPConnectionStep.schema.json | 76 +++ .../api/addContextListenerRequest.schema.json | 65 ++ .../addContextListenerResponse.schema.json | 64 ++ .../api/addEventListenerRequest.schema.json | 53 ++ .../api/addEventListenerResponse.schema.json | 64 ++ .../api/addIntentListenerRequest.schema.json | 46 ++ .../api/addIntentListenerResponse.schema.json | 63 ++ .../3.0.0/api/agentEvent.schema.json | 48 ++ .../3.0.0/api/agentResponse.schema.json | 101 +++ .../schemas-temp/3.0.0/api/api.schema.json | 598 ++++++++++++++++++ .../3.0.0/api/appRequest.schema.json | 71 +++ .../3.0.0/api/broadcastEvent.schema.json | 61 ++ .../3.0.0/api/broadcastRequest.schema.json | 56 ++ .../3.0.0/api/broadcastResponse.schema.json | 29 + .../3.0.0/api/channelChangedEvent.schema.json | 78 +++ .../3.0.0/api/clearContextRequest.schema.json | 56 ++ .../api/clearContextResponse.schema.json | 29 + .../schemas-temp/3.0.0/api/common.schema.json | 59 ++ .../3.0.0/api/contextClearedEvent.schema.json | 49 ++ ...textListenerUnsubscribeRequest.schema.json | 44 ++ ...extListenerUnsubscribeResponse.schema.json | 29 + .../createPrivateChannelRequest.schema.json | 37 ++ .../createPrivateChannelResponse.schema.json | 64 ++ ...ventListenerUnsubscribeRequest.schema.json | 44 ++ ...entListenerUnsubscribeResponse.schema.json | 29 + ...c3UserInterfaceChannelSelected.schema.json | 47 ++ .../api/fdc3UserInterfaceChannels.schema.json | 55 ++ .../api/fdc3UserInterfaceDrag.schema.json | 55 ++ .../fdc3UserInterfaceHandshake.schema.json | 44 ++ .../api/fdc3UserInterfaceHello.schema.json | 62 ++ .../api/fdc3UserInterfaceMessage.schema.json | 34 + .../api/fdc3UserInterfaceResolve.schema.json | 53 ++ ...fdc3UserInterfaceResolveAction.schema.json | 85 +++ .../api/fdc3UserInterfaceRestyle.schema.json | 58 ++ .../api/findInstancesRequest.schema.json | 42 ++ .../api/findInstancesResponse.schema.json | 77 +++ .../3.0.0/api/findIntentRequest.schema.json | 53 ++ .../3.0.0/api/findIntentResponse.schema.json | 72 +++ .../findIntentsByContextRequest.schema.json | 48 ++ .../findIntentsByContextResponse.schema.json | 75 +++ .../api/getAppMetadataRequest.schema.json | 44 ++ .../api/getAppMetadataResponse.schema.json | 72 +++ .../api/getCurrentChannelRequest.schema.json | 37 ++ .../api/getCurrentChannelResponse.schema.json | 67 ++ .../api/getCurrentContextRequest.schema.json | 56 ++ .../api/getCurrentContextResponse.schema.json | 77 +++ .../3.0.0/api/getInfoRequest.schema.json | 37 ++ .../3.0.0/api/getInfoResponse.schema.json | 64 ++ .../api/getOrCreateChannelRequest.schema.json | 44 ++ .../getOrCreateChannelResponse.schema.json | 64 ++ .../api/getUserChannelsRequest.schema.json | 37 ++ .../api/getUserChannelsResponse.schema.json | 67 ++ ...heartbeatAcknowledgmentRequest.schema.json | 46 ++ .../3.0.0/api/heartbeatEvent.schema.json | 38 ++ .../3.0.0/api/intentEvent.schema.json | 59 ++ ...tentListenerUnsubscribeRequest.schema.json | 44 ++ ...entListenerUnsubscribeResponse.schema.json | 29 + .../3.0.0/api/intentResultRequest.schema.json | 58 ++ .../api/intentResultResponse.schema.json | 29 + .../api/joinUserChannelRequest.schema.json | 44 ++ .../api/joinUserChannelResponse.schema.json | 59 ++ .../leaveCurrentChannelRequest.schema.json | 37 ++ .../leaveCurrentChannelResponse.schema.json | 57 ++ .../3.0.0/api/openRequest.schema.json | 52 ++ .../3.0.0/api/openResponse.schema.json | 72 +++ ...ChannelAddEventListenerRequest.schema.json | 57 ++ ...hannelAddEventListenerResponse.schema.json | 64 ++ ...rivateChannelDisconnectRequest.schema.json | 44 ++ ...ivateChannelDisconnectResponse.schema.json | 57 ++ ...annelOnAddContextListenerEvent.schema.json | 59 ++ ...rivateChannelOnDisconnectEvent.schema.json | 46 ++ ...ivateChannelOnUnsubscribeEvent.schema.json | 59 ++ ...nsubscribeEventListenerRequest.schema.json | 44 ++ ...subscribeEventListenerResponse.schema.json | 29 + .../raiseIntentForContextRequest.schema.json | 50 ++ .../raiseIntentForContextResponse.schema.json | 62 ++ .../3.0.0/api/raiseIntentRequest.schema.json | 53 ++ .../3.0.0/api/raiseIntentResponse.schema.json | 97 +++ .../api/raiseIntentResultResponse.schema.json | 68 ++ .../3.0.0/api/t2sQuicktypeUtil.js | 69 ++ .../bridging/agentErrorResponse.schema.json | 40 ++ .../3.0.0/bridging/agentRequest.schema.json | 66 ++ .../3.0.0/bridging/agentResponse.schema.json | 53 ++ .../bridging/bridgeErrorResponse.schema.json | 55 ++ .../3.0.0/bridging/bridgeRequest.schema.json | 51 ++ .../3.0.0/bridging/bridgeResponse.schema.json | 53 ++ .../broadcastAgentRequest.schema.json | 43 ++ .../broadcastBridgeRequest.schema.json | 14 + .../3.0.0/bridging/common.schema.json | 124 ++++ .../3.0.0/bridging/connectionStep.schema.json | 51 ++ .../bridging/connectionStep2Hello.schema.json | 71 +++ .../connectionStep3Handshake.schema.json | 105 +++ ...ctionStep4AuthenticationFailed.schema.json | 57 ++ ...tionStep6ConnectedAgentsUpdate.schema.json | 83 +++ ...indInstancesAgentErrorResponse.schema.json | 34 + .../findInstancesAgentRequest.schema.json | 52 ++ .../findInstancesAgentResponse.schema.json | 31 + ...ndInstancesBridgeErrorResponse.schema.json | 14 + .../findInstancesBridgeRequest.schema.json | 14 + .../findInstancesBridgeResponse.schema.json | 14 + .../findIntentAgentErrorResponse.schema.json | 34 + .../findIntentAgentRequest.schema.json | 40 ++ .../findIntentAgentResponse.schema.json | 34 + .../findIntentBridgeErrorResponse.schema.json | 15 + .../findIntentBridgeRequest.schema.json | 14 + .../findIntentBridgeResponse.schema.json | 15 + ...ntsByContextAgentErrorResponse.schema.json | 34 + ...ndIntentsByContextAgentRequest.schema.json | 42 ++ ...dIntentsByContextAgentResponse.schema.json | 34 + ...tsByContextBridgeErrorResponse.schema.json | 14 + ...dIntentsByContextBridgeRequest.schema.json | 14 + ...IntentsByContextBridgeResponse.schema.json | 14 + ...tAppMetadataAgentErrorResponse.schema.json | 34 + .../getAppMetadataAgentRequest.schema.json | 57 ++ .../getAppMetadataAgentResponse.schema.json | 34 + ...AppMetadataBridgeErrorResponse.schema.json | 14 + .../getAppMetadataBridgeRequest.schema.json | 14 + .../getAppMetadataBridgeResponse.schema.json | 14 + .../openAgentErrorResponse.schema.json | 34 + .../bridging/openAgentRequest.schema.json | 66 ++ .../bridging/openAgentResponse.schema.json | 34 + .../openBridgeErrorResponse.schema.json | 14 + .../bridging/openBridgeRequest.schema.json | 14 + .../bridging/openBridgeResponse.schema.json | 14 + ...teChannelBroadcastAgentRequest.schema.json | 61 ++ ...eChannelBroadcastBridgeRequest.schema.json | 14 + ...EventListenerAddedAgentRequest.schema.json | 59 ++ ...ventListenerAddedBridgeRequest.schema.json | 14 + ...entListenerRemovedAgentRequest.schema.json | 59 ++ ...ntListenerRemovedBridgeRequest.schema.json | 14 + ...AddContextListenerAgentRequest.schema.json | 64 ++ ...ddContextListenerBridgeRequest.schema.json | 14 + ...hannelOnDisconnectAgentRequest.schema.json | 56 ++ ...annelOnDisconnectBridgeRequest.schema.json | 14 + ...annelOnUnsubscribeAgentRequest.schema.json | 63 ++ ...nnelOnUnsubscribeBridgeRequest.schema.json | 14 + .../raiseIntentAgentErrorResponse.schema.json | 34 + .../raiseIntentAgentRequest.schema.json | 60 ++ .../raiseIntentAgentResponse.schema.json | 34 + ...raiseIntentBridgeErrorResponse.schema.json | 14 + .../raiseIntentBridgeRequest.schema.json | 14 + .../raiseIntentBridgeResponse.schema.json | 14 + ...IntentResultAgentErrorResponse.schema.json | 47 ++ ...raiseIntentResultAgentResponse.schema.json | 34 + ...ntentResultBridgeErrorResponse.schema.json | 14 + ...aiseIntentResultBridgeResponse.schema.json | 14 + .../3.0.0/bridgingAsyncAPI/README.md | 38 ++ .../bridgingAsyncAPI/bridgingAsyncAPI.json | 407 ++++++++++++ fdc3-schema/src/main/schemas-temp/README.md | 3 + .../fdc3/api/metadata/ContextMetadata.java | 75 +-- 165 files changed, 8388 insertions(+), 255 deletions(-) delete mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java create mode 100644 fdc3-schema/scripts/prepare-schemas.sh create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/README.md create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP1Hello.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP2LoadUrl.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP3Handshake.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP4ValidateAppIdentity.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityFailedResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP5ValidateAppIdentityResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCP6Goodbye.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WCPConnectionStep.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addContextListenerResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addEventListenerResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/addIntentListenerResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/agentEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/agentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/appRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/broadcastResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/channelChangedEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/clearContextResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/common.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/contextClearedEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/contextListenerUnsubscribeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/createPrivateChannelResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/eventListenerUnsubscribeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannelSelected.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceChannels.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceDrag.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHandshake.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceHello.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceMessage.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolve.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceResolveAction.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/fdc3UserInterfaceRestyle.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findInstancesResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/findIntentsByContextResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getAppMetadataResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentChannelResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getCurrentContextResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getInfoResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getOrCreateChannelResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/getUserChannelsResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatAcknowledgmentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/heartbeatEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/intentEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/intentListenerUnsubscribeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/intentResultResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/joinUserChannelResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/leaveCurrentChannelResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/openRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/openResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelAddEventListenerResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelDisconnectResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnAddContextListenerEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnDisconnectEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelOnUnsubscribeEvent.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/privateChannelUnsubscribeEventListenerResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentForContextResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/raiseIntentResultResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/t2sQuicktypeUtil.js create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/agentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/bridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/broadcastBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/common.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep2Hello.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep3Handshake.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep4AuthenticationFailed.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/connectionStep6ConnectedAgentsUpdate.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findInstancesBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/findIntentsByContextBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/getAppMetadataBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/openBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelBroadcastBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerAddedBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelEventListenerRemovedBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnAddContextListenerBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnDisconnectBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/privateChannelOnUnsubscribeBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultAgentResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeErrorResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridging/raiseIntentResultBridgeResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/README.md create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/bridgingAsyncAPI/bridgingAsyncAPI.json create mode 100644 fdc3-schema/src/main/schemas-temp/README.md 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 index 6c26cf95..9db03234 100644 --- 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 @@ -128,9 +128,10 @@ 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 = ((Map) message.get("meta")).get("timestamp"); - ContextMetadata metadata = ContextMetadataMapper.fromWire(payloadMetadata, messageTimestamp); + 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/util/ContextMetadataMapper.java b/fdc3-agent-proxy/src/main/java/org/finos/fdc3/proxy/util/ContextMetadataMapper.java index f7f52a3a..70942275 100644 --- 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 @@ -23,6 +23,7 @@ import org.finos.fdc3.api.metadata.AppProvidableContextMetadata; import org.finos.fdc3.api.metadata.ContextMetadata; +import org.finos.fdc3.api.types.AppIdentifier; /** * Maps between DACP wire metadata maps and {@link ContextMetadata}. @@ -36,10 +37,27 @@ private ContextMetadataMapper() { * Outbound metadata for DACP request payloads (reference TS: {@code metadata ?? {}}). */ public static Map toWire(AppProvidableContextMetadata metadata) { - return metadata == null ? new LinkedHashMap<>() : ((ContextMetadata) metadata).toMap(); + if (metadata == null) { + return new LinkedHashMap<>(); + } + if (metadata instanceof ContextMetadata) { + return new LinkedHashMap<>((ContextMetadata) metadata); + } + throw new IllegalArgumentException("metadata must be ContextMetadata"); } 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(); @@ -54,6 +72,20 @@ public static ContextMetadata fromWire(Map payloadMetadata, Obje 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)); + } + } } 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 index 08e52e27..aa739a73 100644 --- 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 @@ -19,7 +19,7 @@ import static io.github.robmoffat.support.MatchingUtils.handleResolve; import static io.github.robmoffat.support.MatchingUtils.matchData; -import java.lang.reflect.Method; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,6 +27,7 @@ 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; @@ -59,18 +60,49 @@ public void isAContext(String field, String 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.put("signature", ""); + 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) { + ContextMetadata metadata = defaultBroadcastMetadata(); + metadata.setSignature(new DetachedSignature("test-sig (protected part)", "test-sig (signature part)")); + Map custom = new HashMap<>(); + custom.put("region", "EMEA"); + metadata.setCustom(custom); + + 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<>(); @@ -215,6 +247,24 @@ public void handleContext(Context context, ContextMetadata metadata) { 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") @@ -259,68 +309,53 @@ public void userChannelsOneTwoAndThree() { @When("I destructure methods {string}, {string} from {string}") public void iDestructureMethods(String method1, String method2, String objectField) { Object object = handleResolve(objectField, world); - // Store references to methods for later invocation - world.set("destructured_" + method1, new DestructuredMethod(object, method1)); - world.set("destructured_" + method2, new DestructuredMethod(object, method2)); + 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, new DestructuredMethod(object, methodName)); + 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) { - try { - DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); - Object result = dm.invoke(); - world.set("result", result); - } catch (Exception e) { - world.set("error", e); - world.set("result", null); - } + invokeDestructured(methodName); } - @When("I call destructured {string} with parameter {string}") - public void iCallDestructuredWithParameter(String methodName, String param) { - try { - DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); - Object resolvedParam = handleResolve(param, world); - Object result = dm.invoke(resolvedParam); - world.set("result", result); - } catch (Exception e) { - world.set("error", e); - world.set("result", null); - } + @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} with parameters {string} and {string}") - public void iCallDestructuredWithTwoParameters(String methodName, String param1, String param2) { - try { - DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); - Object resolvedParam1 = handleResolve(param1, world); - Object resolvedParam2 = handleResolve(param2, world); - Object result = dm.invoke(resolvedParam1, resolvedParam2); - world.set("result", result); - } catch (Exception e) { - world.set("error", e); - world.set("result", null); - } + @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)); } - @When("I call destructured {string} with parameters {string} and {string} and {string}") - public void iCallDestructuredWithThreeParameters(String methodName, String param1, String param2, String param3) { + private void invokeDestructured(String methodName, Object... args) { try { DestructuredMethod dm = (DestructuredMethod) world.get("destructured_" + methodName); - Object resolvedParam1 = handleResolve(param1, world); - Object resolvedParam2 = handleResolve(param2, world); - Object resolvedParam3 = handleResolve(param3, world); - Object result = dm.invoke(resolvedParam1, resolvedParam2, resolvedParam3); - world.set("result", result); + if (dm == null) { + throw new IllegalStateException("No destructured method: " + methodName); + } + world.set("result", dm.invoke(args)); } catch (Exception e) { - world.set("error", e); - world.set("result", null); + world.set("result", e); } } 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 index a18908d7..885ad288 100644 --- 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 @@ -33,7 +33,6 @@ public class SchemaSteps { static { Fdc3SchemaMatchers.registerFdc3SchemaMatchers(); - org.finos.fdc3.proxy.support.TestFieldMatchers.class.getName(); } private final PropsWorld world; diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java deleted file mode 100644 index 2a15bb6c..00000000 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/TestFieldMatchers.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.support; - -import static io.github.robmoffat.support.MatchingUtils.handleResolve; -import static io.github.robmoffat.support.MatchingUtils.pathForFieldSuffix; -import static io.github.robmoffat.support.MatchingUtils.registerFieldMatcher; - -import java.util.Map; -import java.util.Objects; -import java.util.regex.Pattern; - -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.JXPathNotFoundException; -import org.finos.fdc3.api.metadata.AntiReplayClaims; -import org.finos.fdc3.api.metadata.ContextMetadata; -import org.finos.fdc3.api.metadata.DetachedSignature; - -import io.github.robmoffat.support.RowFieldMatcher; -import io.github.robmoffat.world.PropsWorld; - -/** - * Field matchers for FDC3-specific assertion columns in Cucumber tables. - */ -public final class TestFieldMatchers { - - private static final String SIGNATURE_SUFFIX = ".signature"; - private static final String IAT_SUFFIX = ".iat"; - - static { - registerFieldMatcher(signatureFieldMatcher()); - registerFieldMatcher(antiReplayIatMatcher()); - } - - private TestFieldMatchers() { - } - - private static RowFieldMatcher signatureFieldMatcher() { - return new RowFieldMatcher() { - @Override - public boolean matchesField(String field) { - return "signature".equals(field) || field.endsWith(SIGNATURE_SUFFIX); - } - - @Override - public boolean matchField(PropsWorld world, String field, String expected, Object rowData) { - String path = "signature".equals(field) ? "signature" : pathForFieldSuffix(field, SIGNATURE_SUFFIX); - if (path == null) { - return false; - } - Object found = valueAtPath(rowData, path); - String actual = signatureValue(found); - Object resolved = handleResolve(expected, world); - if (!Objects.equals(asString(actual), asString(resolved))) { - world.log(String.format("Signature match failed on %s: '%s' vs '%s'", field, actual, resolved)); - return false; - } - return true; - } - }; - } - - private static RowFieldMatcher antiReplayIatMatcher() { - return new RowFieldMatcher() { - @Override - public boolean matchesField(String field) { - return field.endsWith(IAT_SUFFIX); - } - - @Override - public boolean matchField(PropsWorld world, String field, String expected, Object rowData) { - String path = pathForFieldSuffix(field, IAT_SUFFIX); - if (path == null) { - return false; - } - Object found = valueAtPath(rowData, path); - Object resolved = handleResolve(expected, world); - if (!numericEquals(found, resolved)) { - world.log(String.format("Numeric match failed on %s: '%s' vs '%s'", field, found, resolved)); - return false; - } - return true; - } - }; - } - - public static Map metadataToAssertionMap(ContextMetadata metadata) { - if (metadata == null) { - return null; - } - return metadata.toMap(); - } - - private static String signatureValue(Object found) { - if (found == null) { - return null; - } - if (found instanceof DetachedSignature) { - return ((DetachedSignature) found).getSignature(); - } - if (found instanceof Map) { - Object inner = ((Map) found).get("signature"); - return inner == null ? null : String.valueOf(inner); - } - return String.valueOf(found); - } - - private static boolean numericEquals(Object found, Object expected) { - if (found == null && expected == null) { - return true; - } - if (found == null || expected == null) { - return false; - } - if (found instanceof Number && expected instanceof Number) { - return ((Number) found).longValue() == ((Number) expected).longValue(); - } - String foundStr = String.valueOf(found); - String expectedStr = String.valueOf(expected); - if (Pattern.matches("-?\\d+", foundStr) && Pattern.matches("-?\\d+(\\.0)?", expectedStr)) { - return Long.parseLong(foundStr) == (long) Double.parseDouble(expectedStr); - } - return Objects.equals(foundStr, expectedStr); - } - - 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.replace('.', '/'); - return context.getValue(xpathName); - } catch (JXPathNotFoundException e) { - return null; - } - } - - private static String asString(Object value) { - return value == null ? null : String.valueOf(value); - } -} 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 index 5696c404..bf3eef78 100644 --- a/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java +++ b/fdc3-context/src/test/java/org/finos/fdc3/context/ContextRoundTripTest.java @@ -35,16 +35,20 @@ public class ContextRoundTripTest { @BeforeAll static void setUp() { - // Find the schemas directory - it's in target/npm-work/node_modules/@finos/fdc3-context/dist/schemas/context String basePath = System.getProperty("user.dir"); - schemasDir = Paths.get(basePath, "target", "npm-work", "node_modules", - "@finos", "fdc3-context", "dist", "schemas", "context"); - + // 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)) { - // Try relative to project root - schemasDir = Paths.get("fdc3-context", "target", "npm-work", "node_modules", + // 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 diff --git a/fdc3-schema/scripts/prepare-schemas.sh b/fdc3-schema/scripts/prepare-schemas.sh new file mode 100644 index 00000000..fc18227c --- /dev/null +++ b/fdc3-schema/scripts/prepare-schemas.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Copyright FINOS FDC3 contributors - see NOTICE file +# +# Assemble a unified schema layout (api/, bridging/, context/) for quicktype. +# api/ and bridging/ come from the schema source dir; context/ always from npm. + +set -eu + +schemas_work_dir="${1:?usage: prepare-schemas.sh }" +schemas_source_dir="${2:?usage: prepare-schemas.sh }" +context_schemas_dir="${3:?usage: prepare-schemas.sh }" + +mkdir -p "$schemas_work_dir" +ln -sfn "$schemas_source_dir/api" "$schemas_work_dir/api" +ln -sfn "$schemas_source_dir/bridging" "$schemas_work_dir/bridging" +ln -sfn "$context_schemas_dir" "$schemas_work_dir/context" 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/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..10043276 --- /dev/null +++ b/fdc3-schema/src/main/schemas-temp/3.0.0/api/api.schema.json @@ -0,0 +1,598 @@ +{ + "$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": { + "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." + }, + "signature": { + "type": "string", + "title": "signature", + "description": "A cryptographic signature that can be used to verify the authenticity and integrity of the context or intent message." + }, + "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." + } + }, + "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"}, + "signature": {"$ref": "#/definitions/ContextMetadata/properties/signature"}, + "custom": {"$ref": "#/definitions/ContextMetadata/properties/custom"} + }, + "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 + } + } +} 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/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java index 7d57dbf6..2631d454 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/metadata/ContextMetadata.java @@ -17,19 +17,16 @@ import java.time.Instant; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import org.finos.fdc3.api.types.AppIdentifier; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; /** - * Map-backed metadata for context and intent messages. + * Metadata for context and intent messages, stored as a map (same pattern as {@link org.finos.fdc3.api.context.Context}). *

* Implements {@link AppProvidableContextMetadata} for outbound app-provided fields and * {@link DesktopAgentProvidableContextMetadata} for optional Desktop Agent fields. @@ -37,16 +34,18 @@ * typically populated by the Desktop Agent. */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class ContextMetadata implements AppProvidableContextMetadata, DesktopAgentProvidableContextMetadata { - - private final Map data; +public class ContextMetadata extends HashMap + implements AppProvidableContextMetadata, DesktopAgentProvidableContextMetadata { public ContextMetadata() { - this.data = new LinkedHashMap<>(); + super(); } public ContextMetadata(Map initial) { - this.data = initial != null ? new LinkedHashMap<>(initial) : new LinkedHashMap<>(); + super(); + if (initial != null) { + mergeFrom(initial); + } } /** Returns a new empty instance suitable for outbound app-provided metadata. */ @@ -54,32 +53,19 @@ public static ContextMetadata appProvidable() { return new ContextMetadata(); } - /** Returns an unmodifiable view of the backing map. */ - @JsonIgnore + /** Returns an unmodifiable copy of this metadata. */ public Map asMap() { - return Collections.unmodifiableMap(data); + return Collections.unmodifiableMap(new LinkedHashMap<>(this)); } - /** Returns a mutable copy of the backing map (for wire serialization). */ - @JsonIgnore + /** Returns a mutable copy for wire serialization. */ public Map toMap() { - return new LinkedHashMap<>(data); - } - - @JsonAnyGetter - public Map jsonProperties() { - return data; - } - - @JsonAnySetter - public void jsonProperty(String key, Object value) { - data.put(key, value); + return new LinkedHashMap<>(this); } @Override - @JsonProperty("traceId") public String getTraceId() { - return (String) data.get("traceId"); + return (String) get("traceId"); } @Override @@ -88,7 +74,6 @@ public void setTraceId(String traceId) { } @Override - @JsonProperty("signature") public DetachedSignature getSignature() { return getTyped("signature", DetachedSignature.class); } @@ -99,7 +84,6 @@ public void setSignature(DetachedSignature signature) { } @Override - @JsonProperty("antiReplay") public AntiReplayClaims getAntiReplay() { return getTyped("antiReplay", AntiReplayClaims.class); } @@ -110,9 +94,8 @@ public void setAntiReplay(AntiReplayClaims antiReplay) { } @Override - @JsonProperty("authenticity") public String getAuthenticity() { - return (String) data.get("authenticity"); + return (String) get("authenticity"); } @Override @@ -121,9 +104,8 @@ public void setAuthenticity(String authenticity) { } @Override - @JsonProperty("encryption") public String getEncryption() { - return (String) data.get("encryption"); + return (String) get("encryption"); } @Override @@ -133,9 +115,8 @@ public void setEncryption(String encryption) { @Override @SuppressWarnings("unchecked") - @JsonProperty("custom") public Map getCustom() { - Object custom = data.get("custom"); + Object custom = get("custom"); if (custom instanceof Map) { return (Map) custom; } @@ -148,9 +129,8 @@ public void setCustom(Map custom) { } @Override - @JsonProperty("timestamp") public Instant getTimestamp() { - Object value = data.get("timestamp"); + Object value = get("timestamp"); if (value instanceof Instant) { return (Instant) value; } @@ -166,14 +146,13 @@ public void setTimestamp(Instant timestamp) { } @Override - @JsonProperty("source") public AppIdentifier getSource() { - Object source = data.get("source"); + Object source = get("source"); if (source instanceof AppIdentifier) { return (AppIdentifier) source; } if (source instanceof Map) { - return AppIdentifier.fromMap((Map) source); + return AppIdentifier.fromMap(castMap(source)); } return null; } @@ -187,6 +166,9 @@ public static ContextMetadata fromMap(Map map) { if (map == null) { return null; } + if (map instanceof ContextMetadata) { + return (ContextMetadata) map; + } ContextMetadata metadata = new ContextMetadata(); metadata.mergeFrom(map); return metadata; @@ -225,18 +207,23 @@ public void mergeFrom(Map map) { } setAuthenticity((String) map.get("authenticity")); setEncryption((String) map.get("encryption")); + for (Map.Entry entry : map.entrySet()) { + if (!containsKey(entry.getKey())) { + put(entry.getKey(), entry.getValue()); + } + } } private void putOrRemove(String key, Object value) { if (value == null) { - data.remove(key); + remove(key); } else { - data.put(key, value); + put(key, value); } } private T getTyped(String key, Class type) { - Object value = data.get(key); + Object value = get(key); if (type.isInstance(value)) { return type.cast(value); } From dfb7987b80356e4b0008d199fe4a8f14a5451d24 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 5 Jun 2026 10:39:40 +0100 Subject: [PATCH 60/65] Provided local schema compile option --- README.md | 8 + .../finos/fdc3/proxy/schema/LoadSchemas.java | 4 +- .../3.0.0/context/action.schema.json | 103 +++++++++ .../3.0.0/context/chart.schema.json | 86 ++++++++ .../context/chatInitSettings.schema.json | 115 ++++++++++ .../3.0.0/context/chatMessage.schema.json | 43 ++++ .../3.0.0/context/chatRoom.schema.json | 51 +++++ .../context/chatSearchCriteria.schema.json | 73 ++++++ .../3.0.0/context/contact.schema.json | 53 +++++ .../3.0.0/context/contactList.schema.json | 63 ++++++ .../3.0.0/context/context.schema.json | 63 ++++++ .../3.0.0/context/country.schema.json | 62 ++++++ .../3.0.0/context/currency.schema.json | 46 ++++ .../3.0.0/context/email.schema.json | 57 +++++ .../3.0.0/context/fileAttachment.schema.json | 53 +++++ .../3.0.0/context/instrument.schema.json | 121 ++++++++++ .../3.0.0/context/instrumentList.schema.json | 64 ++++++ .../3.0.0/context/interaction.schema.json | 127 +++++++++++ .../3.0.0/context/message.schema.json | 89 ++++++++ .../3.0.0/context/nothing.schema.json | 23 ++ .../3.0.0/context/order.schema.json | 82 +++++++ .../3.0.0/context/orderList.schema.json | 62 ++++++ .../3.0.0/context/organization.schema.json | 59 +++++ .../3.0.0/context/portfolio.schema.json | 79 +++++++ .../3.0.0/context/position.schema.json | 57 +++++ .../3.0.0/context/product.schema.json | 61 +++++ .../3.0.0/context/timeRange.schema.json | 62 ++++++ .../3.0.0/context/trade.schema.json | 67 ++++++ .../3.0.0/context/tradeList.schema.json | 87 ++++++++ .../context/transactionresult.schema.json | 57 +++++ .../3.0.0/context/valuation.schema.json | 59 +++++ fdc3-schema/pom.xml | 208 +++++++++++------- fdc3-schema/scripts/prepare-schemas.sh | 17 -- 33 files changed, 2160 insertions(+), 101 deletions(-) create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/action.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/chart.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/chatInitSettings.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/chatMessage.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/chatRoom.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/chatSearchCriteria.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/contact.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/contactList.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/context.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/country.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/currency.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/email.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/fileAttachment.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/instrument.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/instrumentList.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/interaction.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/message.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/nothing.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/order.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/orderList.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/organization.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/portfolio.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/position.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/product.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/timeRange.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/trade.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/tradeList.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/transactionresult.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/valuation.schema.json delete mode 100644 fdc3-schema/scripts/prepare-schemas.sh diff --git a/README.md b/README.md index 0faf31c8..3df5da25 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,14 @@ This project provides: mvn clean install ``` +This will download the schemas for the DACP / WCP / Contexts from NPM as it runs. However, you can also run like this: + +```sh +mvn clean install -Plocal +``` + +which will use local schemas inside the `src/main/schemas-temp` directories. This is a temporary feature for unreleased versions of FDC3. + ### Maven Dependency Once published, add to your `pom.xml`: 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 index f9be3f6e..d88ae13d 100644 --- 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 @@ -85,7 +85,7 @@ public static Map loadSchemaValidators() throws IOException Path apiDir = resolveApiSchemaDirectory(); if (apiDir == null || !Files.isDirectory(apiDir)) { throw new IllegalStateException( - "Schema directory not found. Build fdc3-schema (npm) or use FDC3 monorepo schemas at " + "Schema directory not found. Build fdc3-schema first (target/schema-work/api) or use FDC3 monorepo schemas at " + "../../../FDC3/packages/fdc3-schema/schemas/api"); } @@ -165,6 +165,7 @@ private static List orderSchemaFiles(Path apiDir) throws IOException { 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"), @@ -179,6 +180,7 @@ private static Path resolveApiSchemaDirectory() { 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"), }; 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..6ded6f73 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/context.schema.json @@ -0,0 +1,63 @@ +{ + "$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..cffd9c4b --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/instrument.schema.json @@ -0,0 +1,121 @@ +{ + "$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" + } + } + }, + "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" + } + } + ] +} 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..19127bc3 --- /dev/null +++ b/fdc3-context/src/schemas-temp/3.0.0/context/interaction.schema.json @@ -0,0 +1,127 @@ +{ + "$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.", + "anyOf": [ + { + "type": "string", + "enum": [ + "Instant Message", + "Email", + "Call", + "Meeting" + ] + }, + { + "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/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-schema/pom.xml b/fdc3-schema/pom.xml index 14beb1f1..282b29e1 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -20,6 +20,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 @@ -73,7 +78,44 @@ - + + + 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 @@ -92,10 +134,68 @@ + + quicktype-api + process-sources + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + delete-duplicate-classes + process-sources + + run + + + + + + + + + + + + + + + + + + + + + + - + com.github.eirslett frontend-maven-plugin @@ -107,7 +207,6 @@ ${project.build.directory}/node-installation - install-node-and-npm generate-sources @@ -115,7 +214,6 @@ install-node-and-npm - npm-init generate-sources @@ -126,7 +224,6 @@ init -y - npm-install-packages generate-sources @@ -134,86 +231,12 @@ npm - install @finos/fdc3-schema@${fdc3.schema.version} @finos/fdc3-context@${fdc3.schema.version} quicktype --save + ${fdc3.npm.schema.packages} - - - org.codehaus.mojo - exec-maven-plugin - 3.1.1 - - - - create-context-symlink - generate-sources - - exec - - - ln - ${project.build.directory}/npm-work - - -sfn - ../../@finos/fdc3-context/dist/schemas/context - node_modules/@finos/fdc3-schema/dist/schemas/context - - - - - - quicktype-api - generate-sources - - exec - - - /bin/sh - ${project.build.directory}/npm-work - - -c - node_modules/.bin/quicktype -s schema --quiet --lang java --package org.finos.fdc3.schema --out ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema/ApiTypes.java -S node_modules/@finos/fdc3-context/dist/schemas/context/context.schema.json $(for f in node_modules/@finos/fdc3-schema/dist/schemas/api/*.schema.json; do echo "--src $f"; done) - - - - - - delete-duplicate-classes - generate-sources - - exec - - - rm - ${project.build.directory}/generated-sources/fdc3/org/finos/fdc3/schema - - -f - Context.java - ContextID.java - ContextType.java - Icon.java - Image.java - DisplayMetadata.java - IntentMetadata.java - ImplementationMetadata.java - OptionalFeatures.java - AppMetadata.java - AppIntent.java - AppIdentifier.java - - - 0 - 1 - - - - - - - com.google.code.maven-replacer-plugin replacer @@ -221,7 +244,7 @@ add-standard-imports - generate-sources + process-sources replace @@ -249,7 +272,6 @@ import org.finos.fdc3.api.types.AppIdentifier; - org.codehaus.mojo build-helper-maven-plugin @@ -257,7 +279,7 @@ import org.finos.fdc3.api.types.AppIdentifier; add-source - generate-sources + process-sources add-source @@ -271,4 +293,24 @@ import org.finos.fdc3.api.types.AppIdentifier; + + + + + download + + true + + + + + + local + + ${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/scripts/prepare-schemas.sh b/fdc3-schema/scripts/prepare-schemas.sh deleted file mode 100644 index fc18227c..00000000 --- a/fdc3-schema/scripts/prepare-schemas.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: Apache-2.0 -# Copyright FINOS FDC3 contributors - see NOTICE file -# -# Assemble a unified schema layout (api/, bridging/, context/) for quicktype. -# api/ and bridging/ come from the schema source dir; context/ always from npm. - -set -eu - -schemas_work_dir="${1:?usage: prepare-schemas.sh }" -schemas_source_dir="${2:?usage: prepare-schemas.sh }" -context_schemas_dir="${3:?usage: prepare-schemas.sh }" - -mkdir -p "$schemas_work_dir" -ln -sfn "$schemas_source_dir/api" "$schemas_work_dir/api" -ln -sfn "$schemas_source_dir/bridging" "$schemas_work_dir/bridging" -ln -sfn "$context_schemas_dir" "$schemas_work_dir/context" From 42c70418656828c049e74db25102e1ecdd2214f6 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 5 Jun 2026 16:55:13 +0100 Subject: [PATCH 61/65] updated features, committed some good changes --- .../fdc3/proxy/channels/DefaultChannel.java | 11 + .../proxy/channels/DefaultChannelSupport.java | 4 + .../proxy/channels/DefaultPrivateChannel.java | 3 - .../listeners/DesktopAgentEventListener.java | 51 +- .../org/finos/fdc3/proxy/RunCucumberTest.java | 2 +- .../finos/fdc3/proxy/TestSpringConfig.java | 3 +- .../finos/fdc3/proxy/steps/ChannelSteps.java | 43 +- .../fdc3/proxy/steps/Fdc3GenericSteps.java | 817 ++++++++++++++++++ .../temporary-features/app-channels.feature | 42 +- .../temporary-features/app-metadata.feature | 4 +- .../temporary-features/broadcast.feature | 28 +- .../temporary-features/find-intents.feature | 12 +- .../intent-listener.feature | 8 +- .../temporary-features/intent-results.feature | 32 +- .../resources/temporary-features/open.feature | 12 +- .../private-channels-deprecated.feature | 12 +- .../private-channels.feature | 26 +- .../temporary-features/raise-intents.feature | 18 +- .../user-channel-sync.feature | 12 +- .../user-channels-set-by-agent.feature | 4 +- .../temporary-features/user-channels.feature | 120 ++- 21 files changed, 1071 insertions(+), 193 deletions(-) create mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java 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 index 1c44a634..649dc21d 100644 --- 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 @@ -20,8 +20,10 @@ 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; @@ -40,6 +42,12 @@ /** * 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 @@ -65,6 +73,7 @@ public DefaultChannel( } @Override + @JsonProperty("id") public String getId() { return id; } @@ -75,12 +84,14 @@ 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; } 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 index 3806f232..3d2afe9c 100644 --- 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 @@ -166,6 +166,10 @@ public CompletionStage addEventListener(EventHandler handler, String t @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()); 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 index 097d22ad..cb41d08a 100644 --- 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 @@ -17,10 +17,7 @@ package org.finos.fdc3.proxy.channels; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - import org.finos.fdc3.api.channel.Channel; import org.finos.fdc3.api.channel.PrivateChannel; import org.finos.fdc3.api.types.ContextHandler; 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 index bb99d824..f2e4744e 100644 --- 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 @@ -81,12 +81,10 @@ protected Map buildSubscribeRequest() { @Override public boolean filter(Map message) { - String type = (String) message.get("type"); if (eventType == null) { - // Listen to all events - return type != null && type.endsWith("Event"); + return message.get("type") != null; } - return getExpectedMessageType().equals(type); + return getExpectedMessageType().equals(message.get("type")); } /** @@ -113,19 +111,17 @@ public void action(Map message) { // Restructure the event based on message type if ("channelChangedEvent".equals(messageType)) { - // Restructure channelChangedEvent to userChannelChanged format Map details = new HashMap<>(); - String newChannelId = (String) payload.get("newChannelId"); - details.put("currentChannelId", newChannelId); - + Object currentChannelId = payload.get("currentChannelId"); + if (currentChannelId == null) { + currentChannelId = payload.get("newChannelId"); + } + details.put("currentChannelId", currentChannelId); + FDC3Event event = new FDC3Event(FDC3Event.Type.USER_CHANNEL_CHANGED, details); handler.handleEvent(event); } else { - // For other event types (currently unused but meeting the spec) - // Convert message type to FDC3Event.Type - FDC3Event.Type fdc3EventType = messageTypeToFDC3EventType(messageType); - FDC3Event event = new FDC3Event(fdc3EventType, payload); - handler.handleEvent(event); + handler.handleEvent(new FDC3Event(messageType, payload)); } } @@ -141,33 +137,4 @@ private FDC3EventType toFDC3SchemaEventType(String eventType) { } } - private FDC3Event.Type toFDC3EventType(String eventType) { - if (eventType == null) { - return null; - } - switch (eventType) { - case "userChannelChanged": - return FDC3Event.Type.USER_CHANNEL_CHANGED; - default: - throw new RuntimeException("UnknownEventType"); - } - } - - /** - * Converts a message type (e.g., "channelChangedEvent") to the corresponding - * FDC3Event.Type enum value (e.g., USER_CHANNEL_CHANGED). - * This is used for messages received from the desktop agent. - */ - private FDC3Event.Type messageTypeToFDC3EventType(String messageType) { - if (messageType == null) { - throw new RuntimeException("UnknownEventType"); - } - switch (messageType) { - case "channelChangedEvent": - return FDC3Event.Type.USER_CHANNEL_CHANGED; - default: - // Currently unused but provided for future event types - throw new RuntimeException("UnknownEventType"); - } - } } 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 index 05cd3ef8..04358f37 100644 --- 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 @@ -35,7 +35,7 @@ @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") + value = "org.finos.fdc3.proxy,org.finos.fdc3.proxy.steps,org.finos.fdc3.proxy.world") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.spring.SpringFactory") @ConfigurationParameter( key = PLUGIN_PROPERTY_NAME, 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 index b586b3bb..8dec2e2e 100644 --- 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 @@ -35,8 +35,7 @@ */ @Configuration @ComponentScan(basePackages = { - "org.finos.fdc3.proxy.steps", - "io.github.robmoffat.steps" + "org.finos.fdc3.proxy.steps" }) public class TestSpringConfig { 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 index aa739a73..aa419ab3 100644 --- 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 @@ -27,13 +27,12 @@ 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.github.robmoffat.steps.GenericSteps; + import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; @@ -78,7 +77,7 @@ public void isABroadcastEventMessage(String field, String channel, String contex @Given("{string} is a BroadcastEvent message on channel {string} with context {string} and metadata") public void isABroadcastEventMessageWithMetadata(String field, String channel, String contextType) { ContextMetadata metadata = defaultBroadcastMetadata(); - metadata.setSignature(new DetachedSignature("test-sig (protected part)", "test-sig (signature part)")); + metadata.put("signature", "test-sig"); Map custom = new HashMap<>(); custom.put("region", "EMEA"); metadata.setCustom(custom); @@ -147,6 +146,42 @@ public void isAChannelChangedEventMessage(String field, String channel) { 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<>(); @@ -372,7 +407,7 @@ public DestructuredMethod(Object target, String methodName) { } public Object invoke(Object... args) throws Exception { - java.lang.reflect.Method method = GenericSteps.findMethod(target.getClass(), methodName, args); + java.lang.reflect.Method method = Fdc3GenericSteps.findMethod(target.getClass(), methodName, args); if (method == null) { throw new NoSuchMethodException("Method not found: " + methodName); } diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java new file mode 100644 index 00000000..7ae73d55 --- /dev/null +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java @@ -0,0 +1,817 @@ +/** + * 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.doesRowMatch; +import static io.github.robmoffat.support.MatchingUtils.handleResolve; +import static io.github.robmoffat.support.MatchingUtils.matchData; +import static io.github.robmoffat.support.MatchingUtils.matchDataAtLeast; +import static io.github.robmoffat.support.MatchingUtils.matchDataDoesntContain; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +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.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.finos.fdc3.api.types.EventHandler; + +import io.github.robmoffat.world.PropsWorld; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +/** + * Generic Cucumber step definitions (local copy of standard-cucumber-steps with FDC3-specific adjustments). + */ +public class Fdc3GenericSteps { + + private final PropsWorld world; + private final Map> jobs = new HashMap<>(); + + public Fdc3GenericSteps(PropsWorld world) { + this.world = world; + } + + // Functional interfaces for multi-arg functions + @FunctionalInterface + public interface ThreeArgFunction { + CompletableFuture apply(Object a, Object b, Object c); + } + + @FunctionalInterface + public interface FourArgFunction { + CompletableFuture apply(Object a, Object b, Object c, Object d); + } + + // ========== Method Invocation (Object.method) Steps ========== + + @When("I call {string} with {string}") + public void iCallWith(String field, String fnName) { + try { + Object object = handleResolve(field, world); + Object result = invokeMethod(object, fnName); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} using argument {string}") + public void iCallWithArgument(String field, String fnName, String param) { + try { + Object object = handleResolve(field, world); + Object paramValue = handleResolve(param, world); + Object result = invokeMethod(object, fnName, paramValue); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} using arguments {string} and {string}") + public void iCallWithTwoArguments(String field, String fnName, String param1, String param2) { + try { + Object object = handleResolve(field, world); + Object result = invokeMethod(object, fnName, handleResolve(param1, world), handleResolve(param2, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} using arguments {string}, {string}, and {string}") + public void iCallWithThreeArguments(String field, String fnName, String param1, String param2, String param3) { + try { + Object object = handleResolve(field, world); + Object result = invokeMethod(object, fnName, + handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} using arguments {string}, {string}, {string}, and {string}") + public void iCallWithFourArguments(String field, String fnName, String param1, String param2, String param3, String param4) { + try { + Object object = handleResolve(field, world); + Object result = invokeMethod(object, fnName, + handleResolve(param1, world), handleResolve(param2, world), + handleResolve(param3, world), handleResolve(param4, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} with {string} as {string}") + public void startMethodJob(String field, String fnName, String jobName) { + startMethodJobWithArgs(field, fnName, jobName); + } + + @When("I call {string} with {string} using argument {string} as {string}") + public void startMethodJobWithArgument(String field, String fnName, String param, String jobName) { + startMethodJobWithArgs(field, fnName, jobName, handleResolve(param, world)); + } + + @When("I call {string} with {string} using arguments {string} and {string} as {string}") + public void startMethodJobWithTwoArguments(String field, String fnName, String param1, String param2, String jobName) { + startMethodJobWithArgs(field, fnName, jobName, handleResolve(param1, world), handleResolve(param2, world)); + } + + @When("I call {string} with {string} using arguments {string}, {string}, and {string} as {string}") + public void startMethodJobWithThreeArguments(String field, String fnName, String param1, String param2, String param3, String jobName) { + startMethodJobWithArgs(field, fnName, jobName, + handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); + } + + @When("I call {string} with {string} using arguments {string}, {string}, {string}, and {string} as {string}") + public void startMethodJobWithFourArguments(String field, String fnName, String param1, String param2, String param3, String param4, String jobName) { + startMethodJobWithArgs(field, fnName, jobName, + handleResolve(param1, world), handleResolve(param2, world), + handleResolve(param3, world), handleResolve(param4, world)); + } + + private void startMethodJobWithArgs(String field, String fnName, String jobName, Object... args) { + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object object = handleResolve(field, world); + return invokeMethod(object, fnName, args); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + // ========== Direct Function Call Steps ========== + + @When("I call {string}") + public void iCallFunction(String fnName) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctional(fn); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} using argument {string}") + public void iCallFunctionWithArgument(String fnName, String param) { + try { + Object fn = handleResolve(fnName, world); + Object paramVal = handleResolve(param, world); + Object result = callFunctionalWithArgs(fn, paramVal); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} using arguments {string} and {string}") + public void iCallFunctionWithTwoArguments(String fnName, String param1, String param2) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, handleResolve(param1, world), handleResolve(param2, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} using arguments {string}, {string}, and {string}") + public void iCallFunctionWithThreeArguments(String fnName, String param1, String param2, String param3) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, + handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + @When("I call {string} using arguments {string}, {string}, {string}, and {string}") + public void iCallFunctionWithFourArguments(String fnName, String param1, String param2, String param3, String param4) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, + handleResolve(param1, world), handleResolve(param2, world), + handleResolve(param3, world), handleResolve(param4, world)); + world.set("result", result); + } catch (Exception error) { + world.set("result", error); + } + } + + // ========== Variable Reference ========== + + @When("I refer to {string} as {string}") + public void iReferToAs(String from, String to) { + world.set(to, handleResolve(from, world)); + } + + // ========== Array Matching Steps ========== + + @Then("{string} is an array of objects with the following contents") + public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { + matchData(world, toList(handleResolve(field, world)), dt); + } + + @Then("{string} is an array of objects with at least the following contents") + public void isAnArrayOfObjectsWithAtLeastContents(String field, DataTable dt) { + matchDataAtLeast(world, toList(handleResolve(field, world)), dt); + } + + @Then("{string} is an array of objects which doesn't contain any of") + public void isAnArrayOfObjectsWhichDoesntContainAnyOf(String field, DataTable dt) { + matchDataDoesntContain(world, toList(handleResolve(field, world)), dt); + } + + @Then("{string} is an array of objects with length {string}") + public void isAnArrayOfObjectsWithLength(String field, String lengthField) { + List data = toList(handleResolve(field, world)); + Object resolved = handleResolve(lengthField, world); + assertEquals(Integer.parseInt(String.valueOf(resolved)), data.size()); + } + + @Then("{string} is an array of strings with the following values") + public void isAnArrayOfStringsWithValues(String field, DataTable dt) { + List data = toList(handleResolve(field, world)); + List> values = data.stream() + .map(s -> Map.of("value", s)) + .collect(Collectors.toList()); + matchData(world, values, dt); + } + + @Then("{string} is an object with the following contents") + public void isAnObjectWithContents(String field, DataTable params) { + List> table = params.asMaps(); + Object data = handleResolve(field, world); + assertTrue(doesRowMatch(world, table.get(0), data)); + } + + // ========== Value Assertions ========== + + @Then("{string} is null") + public void isNull(String field) { + assertNull(handleResolve(field, world)); + } + + @Then("{string} is not null") + public void isNotNull(String field) { + assertNotNull(handleResolve(field, world)); + } + + @Then("{string} is true") + public void isTrue(String field) { + assertTrue(isTruthy(handleResolve(field, world))); + } + + @Then("{string} is false") + public void isFalse(String field) { + assertFalse(isTruthy(handleResolve(field, world))); + } + + @Then("{string} is undefined") + public void isUndefined(String field) { + assertNull(handleResolve(field, world)); + } + + @Then("{string} is empty") + public void isEmpty(String field) { + Object data = handleResolve(field, world); + if (data instanceof List) { + assertTrue(((List) data).isEmpty()); + } else if (data instanceof String) { + assertTrue(((String) data).isEmpty()); + } else if (data == null) { + // null is considered empty + } else { + throw new AssertionError("Expected empty collection or string, got: " + data.getClass().getName()); + } + } + + // ========== Error Assertions ========== + + @Then("{string} is an error with message {string}") + public void isAnErrorWithMessage(String field, String errorType) { + Object value = handleResolve(field, world); + assertTrue(value instanceof Throwable, "Expected a Throwable but got: " + value); + Throwable t = (Throwable) value; + String message = getRootCauseMessage(t); + assertEquals(errorType, message); + } + + @Then("{string} is an error") + public void isAnError(String field) { + assertTrue(handleResolve(field, world) instanceof Throwable); + } + + @Then("{string} is not an error") + public void isNotAnError(String field) { + assertFalse(handleResolve(field, world) instanceof Throwable); + } + + @Then("{string} contains {string}") + public void contains(String field, String substring) { + String actual = String.valueOf(handleResolve(field, world)); + assertTrue(actual.contains(substring), "Expected '" + actual + "' to contain '" + substring + "'"); + } + + @Then("{string} is a string containing one of") + public void isAStringContainingOneOf(String field, DataTable dt) { + String actual = String.valueOf(handleResolve(field, world)); + List> rows = dt.cells(); + List values = rows.stream().skip(1).map(r -> r.get(0)).collect(Collectors.toList()); + boolean found = values.stream().anyMatch(actual::contains); + assertTrue(found, "Expected '" + actual + "' to contain one of: " + values); + } + + @Then("{string} should be greater than {string}") + public void shouldBeGreaterThan(String field, String threshold) { + double actual = toDouble(handleResolve(field, world)); + double thresh = toDouble(handleResolve(threshold, world)); + assertTrue(actual > thresh, "Expected " + actual + " > " + thresh); + } + + @Then("{string} should be less than {string}") + public void shouldBeLessThan(String field, String threshold) { + double actual = toDouble(handleResolve(field, world)); + double thresh = toDouble(handleResolve(threshold, world)); + assertTrue(actual < thresh, "Expected " + actual + " < " + thresh); + } + + // ========== Test Setup ========== + + @Given("{string} is a invocation counter into {string}") + public void isAnInvocationCounter(String handlerName, String counterField) { + world.set(counterField, 0); + world.set(handlerName, (EventHandler) event -> { + int amount = (Integer) world.get(counterField); + world.set(counterField, amount + 1); + }); + } + + @Given("{string} is an async function returning {string}") + public void isAnAsyncFunctionReturning(String fnName, String valueField) { + Object value = handleResolve(valueField, world); + world.set(fnName, (Supplier>) () -> + CompletableFuture.completedFuture(value)); + } + + @Given("{string} is an async function returning {string} after {string} ms") + public void isAnAsyncFunctionReturningAfterDelay(String fnName, String valueField, String delayMs) { + Object value = handleResolve(valueField, world); + long delay = Long.parseLong(delayMs); + world.set(fnName, (Supplier>) () -> + CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(delay); + return value; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + })); + } + + // Setter step: I set "field" to "value" + @Given("I set {string} to {string}") + public void iSetFieldTo(String field, String value) { + world.set(field, handleResolve(value, world)); + } + + // Assertion step: "{field}" is "value" + @Then("{string} is {string}") + public void fieldIs(String field, String value) { + Object actual = handleResolve(field, world); + Object expected = handleResolve(value, world); + assertEquals(String.valueOf(expected), String.valueOf(actual)); + } + + @Given("we wait for a period of {string} ms") + public void weWaitForPeriod(String ms) throws InterruptedException { + Thread.sleep(Long.parseLong(ms)); + } + + // ========== Async Job Steps ========== + + @When("I start {string} as {string}") + public void startJob(String fnName, String jobName) { + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object fn = handleResolve(fnName, world); + return callFunctional(fn); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + @When("I start {string} using argument {string} as {string}") + public void startJobWithArgument(String fnName, String param, String jobName) { + Object paramVal = handleResolve(param, world); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object fn = handleResolve(fnName, world); + return callFunctionalWithArgs(fn, paramVal); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + @When("I start {string} using arguments {string} and {string} as {string}") + public void startJobWithTwoArguments(String fnName, String param1, String param2, String jobName) { + Object p1 = handleResolve(param1, world); + Object p2 = handleResolve(param2, world); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object fn = handleResolve(fnName, world); + return callFunctionalWithArgs(fn, p1, p2); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + @When("I start {string} using arguments {string}, {string}, and {string} as {string}") + public void startJobWithThreeArguments(String fnName, String param1, String param2, String param3, String jobName) { + Object p1 = handleResolve(param1, world); + Object p2 = handleResolve(param2, world); + Object p3 = handleResolve(param3, world); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object fn = handleResolve(fnName, world); + return callFunctionalWithArgs(fn, p1, p2, p3); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + @When("I start {string} using arguments {string}, {string}, {string}, and {string} as {string}") + public void startJobWithFourArguments(String fnName, String param1, String param2, String param3, String param4, String jobName) { + Object p1 = handleResolve(param1, world); + Object p2 = handleResolve(param2, world); + Object p3 = handleResolve(param3, world); + Object p4 = handleResolve(param4, world); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Object fn = handleResolve(fnName, world); + return callFunctionalWithArgs(fn, p1, p2, p3, p4); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + jobs.put(jobName, future); + } + + @Then("I wait for job {string}") + public void waitForJob(String jobName) { + try { + Object result = jobs.get(jobName).get(30, TimeUnit.SECONDS); + world.set("result", result); + world.set(jobName, result); + } catch (Exception e) { + world.set("result", e); + world.set(jobName, e); + } + } + + @Then("I wait for job {string} within {string} ms") + public void waitForJobWithTimeout(String jobName, String timeoutMs) { + try { + long ms = Long.parseLong(timeoutMs); + Object result = jobs.get(jobName).get(ms, TimeUnit.MILLISECONDS); + world.set("result", result); + world.set(jobName, result); + } catch (Exception e) { + world.set("result", e); + world.set(jobName, e); + } + } + + @When("I wait for {string}") + public void iWaitFor(String fnName) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctional(fn); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + @When("I wait for {string} within {string} ms") + public void iWaitForWithTimeout(String fnName, String timeoutMs) { + try { + Object fn = handleResolve(fnName, world); + long ms = Long.parseLong(timeoutMs); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return callFunctional(fn); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + Object result = future.get(ms, TimeUnit.MILLISECONDS); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + @When("I wait for {string} using argument {string}") + public void iWaitForWithArgument(String fnName, String param) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, handleResolve(param, world)); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + @When("I wait for {string} using arguments {string} and {string}") + public void iWaitForWithTwoArguments(String fnName, String param1, String param2) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, handleResolve(param1, world), handleResolve(param2, world)); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + @When("I wait for {string} using arguments {string}, {string}, and {string}") + public void iWaitForWithThreeArguments(String fnName, String param1, String param2, String param3) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, + handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + @When("I wait for {string} using arguments {string}, {string}, {string}, and {string}") + public void iWaitForWithFourArguments(String fnName, String param1, String param2, String param3, String param4) { + try { + Object fn = handleResolve(fnName, world); + Object result = callFunctionalWithArgs(fn, + handleResolve(param1, world), handleResolve(param2, world), + handleResolve(param3, world), handleResolve(param4, world)); + world.set("result", result); + } catch (Exception e) { + world.set("result", e); + } + } + + // ========== Helper Methods ========== + + private Object callFunctional(Object fn) throws Exception { + if (fn instanceof Runnable) { + ((Runnable) fn).run(); + return null; + } + if (fn instanceof java.util.concurrent.Callable) { + Object result = ((java.util.concurrent.Callable) fn).call(); + return resolvePromise(result); + } + if (fn instanceof Supplier) { + Object result = ((Supplier) fn).get(); + return resolvePromise(result); + } + throw new IllegalArgumentException("Not a callable: " + (fn == null ? "null" : fn.getClass().getName())); + } + + private Object callFunctionalWithArgs(Object fn, Object... args) throws Exception { + Method invokeMethod = findMethod(fn.getClass(), "apply", args); + if (invokeMethod == null) { + invokeMethod = findMethod(fn.getClass(), "accept", args); + } + if (invokeMethod == null) { + invokeMethod = findMethod(fn.getClass(), "call", args); + } + if (invokeMethod != null) { + invokeMethod.setAccessible(true); + Object result = invokeMethod.invoke(fn, args); + return resolvePromise(result); + } + throw new IllegalArgumentException("Cannot call " + fn.getClass().getName() + " with " + args.length + " args"); + } + + private Object resolvePromise(Object promise) throws Exception { + if (promise instanceof Supplier) { + promise = ((Supplier) promise).get(); + } + if (promise instanceof CompletionStage) { + return ((CompletionStage) promise).toCompletableFuture().get(30, TimeUnit.SECONDS); + } + return promise; + } + + private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { + Method method = findMethod(target.getClass(), methodName, args); + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + // Convert args to match parameter types + Object[] convertedArgs = convertArgs(method.getParameterTypes(), args); + Object result = method.invoke(target, convertedArgs); + return resolvePromise(result); + } + + private Object[] convertArgs(Class[] paramTypes, Object[] args) { + Object[] converted = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + converted[i] = convertArg(paramTypes[i], args[i]); + } + return converted; + } + + private Object convertArg(Class targetType, Object arg) { + if (arg == null) return null; + + // If already compatible, return as-is + if (wrap(targetType).isAssignableFrom(arg.getClass())) { + return arg; + } + + // Handle numeric conversions + Number num = null; + if (arg instanceof Number) { + num = (Number) arg; + } else if (arg instanceof String) { + try { + num = Double.parseDouble((String) arg); + } catch (NumberFormatException e) { + // Not a number, check for char + if ((targetType == char.class || targetType == Character.class) && ((String) arg).length() == 1) { + return ((String) arg).charAt(0); + } + return arg; + } + } + + if (num != null) { + if (targetType == int.class || targetType == Integer.class) return num.intValue(); + if (targetType == long.class || targetType == Long.class) return num.longValue(); + if (targetType == double.class || targetType == Double.class) return num.doubleValue(); + if (targetType == float.class || targetType == Float.class) return num.floatValue(); + if (targetType == short.class || targetType == Short.class) return num.shortValue(); + if (targetType == byte.class || targetType == Byte.class) return num.byteValue(); + } + + return arg; + } + + public static Method findMethod(Class targetClass, String name, Object... args) { + Method bestMatch = null; + for (Method method : targetClass.getMethods()) { + if (!method.getName().equals(name)) continue; + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != args.length) continue; + if (isCompatible(paramTypes, args)) { + if (bestMatch == null || isMoreSpecific(paramTypes, bestMatch.getParameterTypes())) { + bestMatch = method; + } + } + } + return bestMatch; + } + + private static boolean isCompatible(Class[] paramTypes, Object[] args) { + for (int i = 0; i < paramTypes.length; i++) { + if (args[i] == null) { + if (paramTypes[i].isPrimitive()) return false; + continue; + } + Class wrapped = wrap(paramTypes[i]); + Class argClass = args[i].getClass(); + // Direct assignment compatibility + if (wrapped.isAssignableFrom(argClass)) continue; + // Handle numeric conversions: any Number can be converted to any numeric primitive + if (args[i] instanceof Number && isNumericType(paramTypes[i])) continue; + // Handle String to numeric conversion + if (args[i] instanceof String && isNumericType(paramTypes[i])) { + try { + Double.parseDouble((String) args[i]); + continue; + } catch (NumberFormatException e) { + return false; + } + } + // Handle String to char conversion + if (args[i] instanceof String && (paramTypes[i] == char.class || paramTypes[i] == Character.class)) { + if (((String) args[i]).length() == 1) continue; + } + return false; + } + return true; + } + + private static boolean isNumericType(Class type) { + return type == int.class || type == Integer.class || + type == long.class || type == Long.class || + type == double.class || type == Double.class || + type == float.class || type == Float.class || + type == short.class || type == Short.class || + type == byte.class || type == Byte.class; + } + + private static boolean isMoreSpecific(Class[] a, Class[] b) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i] && b[i].isAssignableFrom(a[i])) return true; + } + return false; + } + + private static Class wrap(Class type) { + if (!type.isPrimitive()) return type; + if (type == int.class) return Integer.class; + if (type == long.class) return Long.class; + if (type == boolean.class) return Boolean.class; + if (type == double.class) return Double.class; + if (type == float.class) return Float.class; + if (type == char.class) return Character.class; + if (type == byte.class) return Byte.class; + if (type == short.class) return Short.class; + return type; + } + + private String getRootCauseMessage(Throwable t) { + Throwable root = t; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + return root.getMessage(); + } + + private boolean isTruthy(Object value) { + if (value == null) return false; + if (value instanceof Boolean) return (Boolean) value; + if (value instanceof Number) return ((Number) value).doubleValue() != 0; + if (value instanceof String) { + String s = (String) value; + if (s.isEmpty()) return false; + try { return Double.parseDouble(s) != 0; } catch (NumberFormatException ignored) {} + return !s.equalsIgnoreCase("false") && !s.equalsIgnoreCase("null"); + } + return true; + } + + private double toDouble(Object value) { + return Double.parseDouble(String.valueOf(value)); + } + + @SuppressWarnings("unchecked") + private List toList(Object obj) { + if (obj == null) return Collections.emptyList(); + if (obj instanceof List) return (List) obj; + if (obj.getClass().isArray()) { + int length = Array.getLength(obj); + List list = new ArrayList<>(length); + for (int i = 0; i < length; i++) list.add(Array.get(obj, i)); + return list; + } + throw new IllegalArgumentException("Expected array or List, but got: " + obj.getClass().getName()); + } +} 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 index 5da42e88..02314d13 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature @@ -10,10 +10,10 @@ Feature: Channel Listeners Support 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" + When I call "{api1}" with "getOrCreateChannel" with parameter "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 I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -26,9 +26,9 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Unsubscribing a context listener prevents it collecting data. - When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + When I call "{api1}" with "getOrCreateChannel" with parameter "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" with parameters "fdc3.instrument" and "{resultHandler}" And I call "{result}" with "unsubscribe" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is empty @@ -40,9 +40,9 @@ Feature: Channel Listeners Support 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" + When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" using argument "{resultHandler}" + And I call "{channel1}" with "addContextListener" with parameter "{resultHandler}" And messaging receives "{instrumentMessageOne}" And messaging receives "{countryMessageOne}" Then "{contexts}" is an array of objects with the following contents @@ -56,9 +56,9 @@ Feature: Channel Listeners Support 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" + When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" using arguments "{null}" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" And messaging receives "{countryMessageOne}" Then "{contexts}" is an array of objects with the following contents @@ -71,20 +71,20 @@ Feature: Channel Listeners Support | 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" + When I call "{api1}" with "getOrCreateChannel" with parameter "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}" + And I call "{channel1}" with "addContextListener" with parameters "{true}" and "{resultHandler}" Then "{result}" is an error - And I call "{channel1}" with "addContextListener" using arguments "{null}" and "{true}" + And I call "{channel1}" with "addContextListener" with parameters "{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" + When I call "{api1}" with "getOrCreateChannel" with parameter "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 I call destructured "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -95,21 +95,21 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Destructured getCurrentContext after broadcast - When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" + When I call "{api1}" with "getOrCreateChannel" with parameter "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" + And I call destructured "broadcast" with parameter "{instrumentContext}" + And I call destructured "getCurrentContext" with parameter "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" + When I call "{api1}" with "getOrCreateChannel" with parameter "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 I call destructured "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -117,9 +117,9 @@ Feature: Channel Listeners Support 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" + When I call "{api1}" with "getOrCreateChannel" with parameter "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" with parameters "fdc3.instrument" and "{resultHandler}" 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/app-metadata.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature index ebcc4f71..1a5cdbef 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature @@ -6,7 +6,7 @@ Feature: Desktop Agent Information And app "chipShop/c1" Scenario: Getting App metadata - When I call "{api}" with "getAppMetadata" using argument "{c1}" + When I call "{api}" with "getAppMetadata" with parameter "{c1}" Then "{result}" is an object with the following contents | appId | name | description | | chipShop | Metadata Name | Metadata Description | @@ -24,7 +24,7 @@ Feature: Desktop Agent Information | cucumber-app | cucumber-instance | Scenario: Getting instance information - When I call "{api}" with "findInstances" using argument "{c1}" + When I call "{api}" with "findInstances" with parameter "{c1}" Then "{result}" is an array of objects with the following contents | appId | instanceId | | One | 1 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature index 051a25c2..40f62599 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature @@ -9,21 +9,21 @@ Feature: Broadcasting Given "instrumentContext" is a "fdc3.instrument" context Scenario: Broadcasting on a named app channel - When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" using argument "{instrumentContext}" + And I call "{channel1}" with "broadcast" with parameter "{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}" + When I call "{api}" with "broadcast" with parameter "{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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "broadcast" with parameter "{instrumentContext}" Then messaging will have posts | payload.channelId | payload.context.type | payload.context.name | matches_type | | one | {null} | {null} | joinUserChannelRequest | @@ -32,9 +32,9 @@ Feature: Broadcasting 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" + When I call "{api}" with "getOrCreateChannel" with parameter "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" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -46,25 +46,25 @@ Feature: Broadcasting 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" + When I call "{api}" with "getOrCreateChannel" with parameter "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" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{fullMetadataMessage}" Then "{metadatas}" is an array of objects with the following contents | source.appId | source.instanceId | signature | custom.region | | cucumber-app | cucumber-instance | test-sig | EMEA | Scenario: getCurrentContextWithMetadata returns context and metadata - When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + When I call "{api}" with "getOrCreateChannel" with parameter "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" + And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" + And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" Then "{result}" is an object with the following contents | context.type | context.name | metadata.source.appId | metadata.traceId | metadata.signature | metadata.custom.key | | fdc3.instrument | Apple | test-app | test-trace-id | test-signature | value | Scenario: getCurrentContextWithMetadata returns null for empty channel - When I call "{api}" with "getOrCreateChannel" using argument "channel-name" + When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "getCurrentContextWithMetadata" using argument "fdc3.instrument" + And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "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 index fce0f0aa..d7fc546b 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature @@ -13,7 +13,7 @@ Feature: Basic Intents Support 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" + When I call "{api}" with "findIntent" with parameter "Buy" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -26,14 +26,14 @@ Feature: Basic Intents Support | Buy | findIntentRequest | Scenario: Find Intent can return an error when an intent doesn't match - When I call "{api}" with "findIntent" using argument "Bob" + When I call "{api}" with "findIntent" with parameter "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}" + When I call "{api}" with "findIntent" with parameters "Buy" and "{instrumentContext}" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -45,7 +45,7 @@ Feature: Basic Intents Support | 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" + When I call "{api}" with "findIntent" with parameters "OrderFood" and "{empty}" and "channel" Then "{result.intent}" is an object with the following contents | name | | OrderFood | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | OrderFood | channel | findIntentRequest | Scenario: Find Intents By Context - When I call "{api}" with "findIntentsByContext" using argument "{instrumentContext}" + When I call "{api}" with "findIntentsByContext" with parameter "{instrumentContext}" Then "{result}" is an array of objects with the following contents | intent.name | apps[0].appId | apps.length | | Buy | bank | 1 | @@ -67,7 +67,7 @@ Feature: Basic Intents Support | 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}" + When I call "{api}" with "findIntentsByContext" with parameter "{crazyContext}" Then "{result}" is an error with message "NoAppsFound" And messaging will have posts | payload.context.type | payload.context.bogus | matches_type | 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 index bca04b89..798963fd 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature @@ -8,7 +8,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Work Given "resultHandler" pipes intent to "intents" - When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + When I call "{api1}" with "addIntentListener" with parameters "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 | @@ -19,7 +19,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Context) Given "resultHandler" returns a context item - When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" And messaging receives "{intentMessageOne}" Then messaging will have posts | type | payload.intentResult.context.type | payload.intentResolution.intent | @@ -27,7 +27,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Channel) Given "resultHandler" returns a channel - When I call "{api1}" with "addIntentListener" using arguments "BuyStock" and "{resultHandler}" + When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" And messaging receives "{intentMessageOne}" Then messaging will have posts | type | payload.intentResult.channel.type | payload.intentResult.channel.id | @@ -35,7 +35,7 @@ Feature: Intent Listeners 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}" + When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" And messaging receives "{intentMessageOne}" Then messaging will have posts | type | payload.intentResult.channel | payload.intentResult.context | 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 index 266232d0..eb24e57f 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature @@ -8,17 +8,17 @@ Feature: Intents Can Return Different Results Scenario: Raise Intent times out Given Raise Intent times out - When I call "{api}" with "raiseIntent" using arguments "OrderFood" and "{instrumentContext}" + When I call "{api}" with "raiseIntent" with parameters "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}" + When I call "{api}" with "raiseIntent" with parameters "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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResult" Then "{result}" is undefined And messaging will have posts @@ -26,7 +26,7 @@ Feature: Intents Can Return Different Results | 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | intent | | chipShop | c1 | OrderFood | @@ -36,7 +36,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResult" Then "{result}" is an object with the following contents | type | name | @@ -47,7 +47,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResult" Then "{result}" is an object with the following contents | type | id | @@ -58,7 +58,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResult" Then "{result}" is an object with the following contents | type | id | @@ -69,7 +69,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResult" Then "{result}" is an object with the following contents | type | id | @@ -81,7 +81,7 @@ Feature: Intents Can Return Different Results 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 call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I destructure method "getResult" from "{result}" And I call destructured "getResult" Then "{result}" is an object with the following contents @@ -93,7 +93,7 @@ Feature: Intents Can Return Different Results 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}" + And I call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | intent | | chipShop | c1 | OrderFood | @@ -104,7 +104,7 @@ Feature: Intents Can Return Different Results 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 call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I destructure method "getResult" from "{result}" And I call destructured "getResult" Then "{result}" is an object with the following contents @@ -117,7 +117,7 @@ Feature: Intents Can Return Different Results 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 call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I destructure method "getResult" from "{result}" And I call destructured "getResult" Then "{result}" is an object with the following contents @@ -129,7 +129,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResultMetadata" Then "{result}" is an object with the following contents | source.appId | source.instanceId | @@ -137,7 +137,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "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 | @@ -145,7 +145,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResultMetadata" Then "{result}" is an object with the following contents | source.appId | source.instanceId | @@ -153,7 +153,7 @@ Feature: Intents Can Return Different Results 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" And I call "{result}" with "getResultMetadata" Then "{result}" is an object with the following contents | source.appId | source.instanceId | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature index 5aa2c5a9..cc57afab 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature @@ -7,7 +7,7 @@ Feature: Desktop Agent Information And "instrumentContext" is a "fdc3.instrument" context Scenario: Open An App - When I call "{api}" with "open" using arguments "{c1}" and "{instrumentContext}" + When I call "{api}" with "open" with parameters "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -16,7 +16,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Open An App Using App ID - When I call "{api}" with "open" using arguments "chipShop" and "{instrumentContext}" + When I call "{api}" with "open" with parameters "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -25,7 +25,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Opening a non-existent App - When I call "{api}" with "open" using arguments "nonExistent" and "{instrumentContext}" + When I call "{api}" with "open" with parameters "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 | @@ -33,7 +33,7 @@ Feature: Desktop Agent Information Scenario: Open An App - Destructured When I destructure method "open" from "{api}" - And I call destructured "open" using arguments "{c1}" and "{instrumentContext}" + And I call destructured "open" with parameters "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -43,7 +43,7 @@ Feature: Desktop Agent Information 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}" + And I call destructured "open" with parameters "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -53,7 +53,7 @@ Feature: Desktop Agent Information 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}" + When I call "{api}" with "open" with parameters "{c1}" and "{null}" and "{openMetadata}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | 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 index 0f36b274..d03f459d 100644 --- 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 @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onAddContextListener" with parameter "{typesHandler}" And I refer to "{result}" as "theListener" And we wait for a period of "100" ms And I call "{theListener}" with "unsubscribe" @@ -22,7 +22,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onAddContextListener" with parameter "{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 @@ -31,7 +31,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onUnsubscribe" with parameter "{typesHandler}" And we wait for a period of "100" ms And I refer to "{result}" as "theListener" And I call "{theListener}" with "unsubscribe" @@ -43,7 +43,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onUnsubscribe" with parameter "{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 @@ -52,7 +52,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onDisconnect" with parameter "{voidHandler}" And I refer to "{result}" as "theListener" And we wait for a period of "100" ms And I call "{theListener}" with "unsubscribe" @@ -64,7 +64,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "onDisconnect" with parameter "{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 index e937fdac..8071b50d 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addContextListener" with parameters "fdc3.instrument" and "{contextHandler}" And I refer to "{result}" as "theListener" And I call "{theListener}" with "unsubscribe" Then messaging will have posts @@ -20,7 +20,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -28,7 +28,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -39,7 +39,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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" @@ -51,7 +51,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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 @@ -60,7 +60,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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" @@ -72,7 +72,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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 @@ -84,7 +84,7 @@ Feature: Basic Private Channels Support 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 I call "{privateChannel}" with "addEventListener" with parameters "{null}" and "{typesHandler}" And we wait for a period of "100" ms And messaging receives "{onAddContextListenerMessage}" And messaging receives "{onUnsubscribeListenerMessage}" @@ -93,7 +93,7 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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" @@ -105,14 +105,14 @@ Feature: Basic Private Channels Support 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}" + When I call "{privateChannel}" with "addEventListener" with parameters "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}" + When I call "{privateChannel}" with "broadcast" with parameter "{instrumentContext}" Then messaging will have posts | type | payload.channelId | payload.context.type | payload.context.name | matches_type | | broadcastRequest | {privateChannel.id} | fdc3.instrument | Apple | broadcastRequest | @@ -135,8 +135,8 @@ Feature: Basic Private Channels Support 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 I call destructured "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + And I call destructured "broadcast" with parameter "{instrumentContext}" 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/raise-intents.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature index d79bbc1f..bc773f22 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature @@ -18,7 +18,7 @@ Feature: Basic Intents Support 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}" + When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | chipShop | c1 | @@ -28,14 +28,14 @@ Feature: Basic Intents Support | 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}" + When I call "{api}" with "raiseIntent" with parameters "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}" + When I call "{api}" with "raiseIntent" with parameters "Buy" and "{instrumentContext}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | bank | b1 | @@ -47,7 +47,7 @@ Feature: Basic Intents Support The intent resolver will just take the first matching application that would resolve an intent. - When I call "{api}" with "raiseIntentForContext" using argument "{instrumentContext}" + When I call "{api}" with "raiseIntentForContext" with parameter "{instrumentContext}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | chipShop | c1 | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | 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}" + When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{t1}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | travelAgent | t1 | @@ -66,7 +66,7 @@ Feature: Basic Intents Support | 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}" + When I call "{api}" with "raiseIntentForContext" with parameter "{cancelContext}" Then "{result}" is an error with message "UserCancelledResolution" And messaging will have posts | payload.context.type | matches_type | @@ -74,7 +74,7 @@ Feature: Basic Intents Support 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}" + When I call "{api}" with "raiseIntent" with parameters "Buy" and "{instrumentContext}" and "{null}" and "{intentMetadata}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | bank | b1 | @@ -83,14 +83,14 @@ Feature: Basic Intents Support | 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}" + When I call "{api}" with "raiseIntent" with parameters "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}" + When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{null}" and "{intentMetadata}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | chipShop | c1 | 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 index ca11b6a1..845e0a3d 100644 --- 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 @@ -11,14 +11,14 @@ Feature: Updating User Channel State 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 I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "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" + And I call "{result}" with "getCurrentContext" with parameter "fdc3.instrument" Then "{result}" is an object with the following contents | type | name | | fdc3.instrument | Apple | @@ -31,12 +31,12 @@ Feature: Updating User Channel State 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" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + When I call "{api}" with "joinUserChannel" with parameter "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" + And I call "{result}" with "getCurrentContext" with parameter "fdc3.instrument" Then "{result}" is null Scenario: disconnection 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 index c74bb6bc..367fcbad 100644 --- 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 @@ -16,7 +16,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel 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 I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -28,7 +28,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel 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 "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And I call "{api}" with "leaveCurrentChannel" Then messaging will have posts | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | 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 index 6727c16f..2e5fdca7 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature @@ -51,7 +51,7 @@ Feature: Basic User Channels Support 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 "joinUserChannel" with parameter "one" When I call "{api}" with "getCurrentChannel" Then "{result}" is an object with the following contents | id | type | displayMetadata.color | @@ -64,7 +64,7 @@ Feature: Basic User Channels Support 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 "joinChannel" with parameter "one" When I call "{api}" with "getCurrentChannel" Then "{result}" is an object with the following contents | id | type | displayMetadata.color | @@ -76,8 +76,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -91,8 +91,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "{empty}" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -106,8 +106,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameter "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -121,7 +121,7 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is empty And messaging will have posts @@ -130,8 +130,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And I refer to "{result}" as "theListener" And messaging receives "{instrumentMessageOne}" And I call "{theListener}" with "unsubscribe" @@ -149,8 +149,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And I call "{api}" with "leaveCurrentChannel" Then messaging will have posts | payload.channelId | payload.contextType | payload.listenerUUID | matches_type | @@ -164,24 +164,24 @@ Feature: Basic User Channels Support | 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" + When I call "{api}" with "joinUserChannel" with parameter "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}" + When I call "{api}" with "addContextListener" with parameters "{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}" + And I call "{api}" with "addContextListener" with parameters "{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" + When I call "{api}" with "joinUserChannel" with parameter "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 "{api}" with "broadcast" with parameter "{instrumentContext}" And I call "{theChannel}" with "getCurrentContext" Then "{result}" is an object with the following contents | id.ticker | type | name | @@ -195,17 +195,17 @@ Feature: Basic User Channels Support 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" + When I call "{api}" with "joinUserChannel" with parameter "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" + And I call "{theChannel}" with "getCurrentContext" with parameter "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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And I refer to "{result}" as "theListener" When messaging receives "{userChannelMessage2}" # Channel changed event handling is async @@ -216,7 +216,7 @@ Feature: Basic User Channels Support | 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 I call "{api}" with "joinUserChannel" with parameter "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 @@ -224,7 +224,7 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "addEventListener" with parameters "userChannelChanged" and "{typesHandler}" And I refer to "{result}" as "theListener" And messaging receives "{userChannelMessage2}" And messaging receives "{userChannelMessage1}" @@ -244,7 +244,7 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "addEventListener" with parameters "{null}" and "{typesHandler}" And I refer to "{result}" as "theListener" And messaging receives "{userChannelMessage2}" And messaging receives "{userChannelMessage1}" @@ -264,9 +264,57 @@ Feature: Basic User Channels Support Scenario: Adding An Unknown Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" using arguments "unknownEventType" and "{typesHandler}" + When I call "{api}" with "addEventListener" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "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" with parameters "{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" @@ -278,7 +326,7 @@ Feature: Basic User Channels Support Scenario: Destructured joinUserChannel and getCurrentChannel work correctly When I destructure method "joinUserChannel" from "{api}" - And I call destructured "joinUserChannel" using argument "one" + And I call destructured "joinUserChannel" with parameter "one" And I destructure method "getCurrentChannel" from "{api}" And I call destructured "getCurrentChannel" Then "{result}" is an object with the following contents @@ -291,12 +339,12 @@ Feature: Basic User Channels Support Scenario: Destructured channel getCurrentContext after broadcast Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" using argument "one" + When I call "{api}" with "joinUserChannel" with parameter "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 "broadcast" with parameter "{instrumentContext}" And I call destructured "getCurrentContext" Then "{result}" is an object with the following contents | id.ticker | type | name | @@ -305,8 +353,8 @@ Feature: Basic User Channels Support 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 "joinUserChannel" with parameter "one" + And I call destructured "broadcast" with parameter "{instrumentContext}" And I call "{api}" with "getCurrentChannel" And I refer to "{result}" as "theChannel" And I call "{theChannel}" with "getCurrentContext" @@ -317,13 +365,13 @@ Feature: Basic User Channels Support 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 I call "{api}" with "joinUserChannel" with parameter "one" + And I call destructured "addContextListener" with parameters "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 I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{openMessage}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | @@ -334,8 +382,8 @@ Feature: Basic User Channels Support 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}" + When I call "{api}" with "joinUserChannel" with parameter "one" + And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" And messaging receives "{instrumentMessageOne}" Then "{contexts}" is an array of objects with the following contents | id.ticker | type | name | From b4819f6aa6c7e3f49ff353917f1b56d86be60a97 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Fri, 5 Jun 2026 17:29:27 +0100 Subject: [PATCH 62/65] updated schema --- .../3.0.0/context/context.schema.json | 8 +- .../3.0.0/context/instrument.schema.json | 49 +++++++ .../3.0.0/context/interaction.schema.json | 15 +- .../security.encryptedContext.schema.json | 66 +++++++++ .../security.symmetricKeyRequest.schema.json | 45 ++++++ .../security.symmetricKeyResponse.schema.json | 59 ++++++++ .../3.0.0/context/security.user.schema.json | 31 ++++ .../context/security.userRequest.schema.json | 37 +++++ .../schemas-temp/3.0.0/api/api.schema.json | 138 ++++++++++++++++-- 9 files changed, 421 insertions(+), 27 deletions(-) create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/security.encryptedContext.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyRequest.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/security.symmetricKeyResponse.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/security.user.schema.json create mode 100644 fdc3-context/src/schemas-temp/3.0.0/context/security.userRequest.schema.json 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 index 6ded6f73..c67aadd5 100644 --- 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 @@ -25,7 +25,9 @@ }, "id": { "type": "object", - "unevaluatedProperties": {"type": "string" } + "unevaluatedProperties": { + "type": "string" + } } }, "additionalProperties": true, @@ -51,7 +53,9 @@ "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" } + "unevaluatedProperties": { + "type": "string" + } } }, "additionalProperties": true, 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 index cffd9c4b..81fa02ab 100644 --- 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 @@ -96,6 +96,52 @@ "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": [ @@ -115,6 +161,9 @@ }, "market": { "MIC": "XNAS" + }, + "classification": { + "name": "publicCompany" } } ] 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 index 19127bc3..a07616f2 100644 --- 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 @@ -46,20 +46,7 @@ "interactionType": { "title": "Interaction Type", "description": "`interactionType` SHOULD be one of `'Instant Message'`, `'Email'`, `'Call'`, or `'Meeting'` although other string values are permitted.", - "anyOf": [ - { - "type": "string", - "enum": [ - "Instant Message", - "Email", - "Call", - "Meeting" - ] - }, - { - "type": "string" - } - ] + "type": "string" }, "description": { "title": "Interaction Description", 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-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 index 10043276..c5963d27 100644 --- 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 @@ -3,6 +3,98 @@ "$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", @@ -288,16 +380,23 @@ "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." }, - "signature": { - "type": "string", - "title": "signature", - "description": "A cryptographic signature that can be used to verify the authenticity and integrity of the context or intent message." - }, "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": [ @@ -312,9 +411,20 @@ "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"}, - "signature": {"$ref": "#/definitions/ContextMetadata/properties/signature"}, - "custom": {"$ref": "#/definitions/ContextMetadata/properties/custom"} + "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 }, @@ -564,7 +674,10 @@ "additionalProperties": true } }, - "required":["type","details"], + "required": [ + "type", + "details" + ], "additionalProperties": false }, "PrivateChannelEventType": { @@ -591,8 +704,11 @@ "additionalProperties": true } }, - "required":["type","details"], + "required": [ + "type", + "details" + ], "additionalProperties": false } } -} +} \ No newline at end of file From 6cf74303dc3372a39a228804f5ea09bdf2ac3ee9 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Sun, 7 Jun 2026 12:16:18 +0100 Subject: [PATCH 63/65] All proxy tests working --- fdc3-agent-proxy/pom.xml | 2 +- .../fdc3/proxy/channels/DefaultChannel.java | 42 +++++---- .../proxy/channels/DefaultChannelSupport.java | 91 +++++++++++-------- .../DefaultUserChannelContextListener.java | 18 ++-- .../channels/UserChannelContextListener.java | 8 +- .../proxy/intents/DefaultIntentSupport.java | 4 +- .../listeners/DesktopAgentEventListener.java | 11 ++- .../proxy/util/ContextMetadataMapper.java | 75 ++++++++++++++- .../finos/fdc3/proxy/steps/ChannelSteps.java | 22 +++-- .../fdc3/proxy/steps/Fdc3GenericSteps.java | 5 +- .../finos/fdc3/proxy/steps/IntentSteps.java | 60 +++++++----- .../responses/ChannelStateResponse.java | 5 +- .../temporary-features/app-channels.feature | 42 ++++----- .../temporary-features/app-metadata.feature | 4 +- .../temporary-features/broadcast.feature | 36 ++++---- .../temporary-features/find-intents.feature | 12 +-- .../intent-listener.feature | 8 +- .../temporary-features/intent-results.feature | 32 +++---- .../resources/temporary-features/open.feature | 12 +-- .../private-channels-deprecated.feature | 12 +-- .../private-channels.feature | 26 +++--- .../temporary-features/raise-intents.feature | 18 ++-- .../user-channel-sync.feature | 12 +-- .../user-channels-set-by-agent.feature | 4 +- .../temporary-features/user-channels.feature | 82 ++++++++--------- .../org/finos/fdc3/api/types/FDC3Event.java | 26 +++++- 26 files changed, 408 insertions(+), 261 deletions(-) diff --git a/fdc3-agent-proxy/pom.xml b/fdc3-agent-proxy/pom.xml index a81e6a49..d3018941 100644 --- a/fdc3-agent-proxy/pom.xml +++ b/fdc3-agent-proxy/pom.xml @@ -43,7 +43,7 @@ io.github.robmoffat standard-cucumber-steps - 1.1.0 + 1.2.1 test 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 index 649dc21d..07d37c08 100644 --- 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 @@ -147,14 +147,8 @@ public CompletionStage> getCurrentContext(String contextType) return messaging.>exchange(requestMap, "getCurrentContextResponse", messageExchangeTimeout) .thenApply(response -> { - GetCurrentContextResponse typedResponse = messaging.getConverter() - .convertValue(response, GetCurrentContextResponse.class); - - if (typedResponse.getPayload() != null && - typedResponse.getPayload().getContext() != null) { - return Optional.of(typedResponse.getPayload().getContext()); - } - return Optional.empty(); + Context context = extractContextFromResponse(response); + return context != null ? Optional.of(context) : Optional.empty(); }); } @@ -174,17 +168,14 @@ public CompletionStage> getCurrentContextWithMetad return messaging.>exchange(requestMap, "getCurrentContextResponse", messageExchangeTimeout) .thenApply(response -> { - GetCurrentContextResponse typedResponse = messaging.getConverter() - .convertValue(response, GetCurrentContextResponse.class); - - if (typedResponse.getPayload() == null - || typedResponse.getPayload().getContext() == null) { + Context context = extractContextFromResponse(response); + if (context == null) { return Optional.empty(); } - Context context = typedResponse.getPayload().getContext(); - Map responseMap = response; - Map responsePayload = (Map) responseMap.get("payload"); + GetCurrentContextResponse typedResponse = messaging.getConverter() + .convertValue(response, GetCurrentContextResponse.class); + Map responsePayload = (Map) response.get("payload"); Map payloadMetadata = responsePayload != null ? (Map) responsePayload.get("metadata") : null; @@ -219,4 +210,23 @@ protected CompletionStage addContextListenerInner(String contextType, ); 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 index 3d2afe9c..bd375887 100644 --- 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 @@ -100,6 +100,31 @@ public CompletionStage connect() { 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") @@ -107,47 +132,34 @@ private CompletionStage registerUserChannelChangedListener() { String newChannelId = details != null ? (String) details.get("currentChannelId") : null; Logger.debug("Desktop Agent reports channel changed: {}", newChannelId); - getUserChannelsCached().thenAccept(channels -> { - Channel theChannel = null; + applyCurrentChannelFromId(newChannelId); - if (newChannelId != null) { - theChannel = channels.stream() - .filter(c -> newChannelId.equals(c.getId())) - .findFirst() - .orElse(null); + 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); + } - if (theChannel == null) { - Logger.debug( - "Unknown user channel, querying Desktop Agent for updated user channels: {}", - newChannelId); - getUserChannels().thenAccept(updatedChannels -> { - Channel foundChannel = updatedChannels.stream() - .filter(c -> newChannelId.equals(c.getId())) - .findFirst() - .orElse(null); - - if (foundChannel == null) { - Logger.warn( - "Received user channel update with unknown user channel (user channel listeners will not work): {}", - newChannelId); - } - - currentChannel = foundChannel; - channelSelector.updateChannel( - foundChannel != null ? foundChannel.getId() : null, updatedChannels); - for (UserChannelContextListener listener : userChannelListeners) { - listener.changeChannel(); - } - }); - return; - } + 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 = theChannel; - channelSelector.updateChannel(theChannel != null ? theChannel.getId() : null, channels); + currentChannel = channel; + return getUserChannelsCached(); + }).thenCompose(channels -> { + channelSelector.updateChannel(currentChannel != null ? currentChannel.getId() : null, channels); + CompletionStage notify = CompletableFuture.completedFuture(null); for (UserChannelContextListener listener : userChannelListeners) { - listener.changeChannel(); + notify = notify.thenCompose(v -> listener.changeChannel()); } + return notify; }); }, "userChannelChanged").thenApply(listener -> null); } @@ -319,7 +331,7 @@ public CompletionStage joinUserChannel(String id) { return messaging.>exchange(requestMap, "joinUserChannelResponse", messageExchangeTimeout) .thenCompose(response -> getUserChannelsCached()) - .thenAccept(channels -> { + .thenCompose(channels -> { currentChannel = channels.stream() .filter(c -> id.equals(c.getId())) .findFirst() @@ -331,10 +343,11 @@ public CompletionStage joinUserChannel(String id) { channelSelector.updateChannel(id, channels); - // Notify all user channel listeners of the channel change + CompletionStage notify = CompletableFuture.completedFuture(null); for (UserChannelContextListener listener : userChannelListeners) { - listener.changeChannel(); + notify = notify.thenCompose(v -> listener.changeChannel()); } + return notify; }); } 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 index 3c095681..1441e0be 100644 --- 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 @@ -46,10 +46,7 @@ public DefaultUserChannelContextListener( @Override public CompletionStage register() { - return super.register().thenCompose(v -> { - changeChannel(); - return CompletableFuture.completedFuture(null); - }); + return super.register().thenCompose(v -> changeChannel()); } /** @@ -57,14 +54,15 @@ public CompletionStage register() { * current channel and notifies the handler if there is one. */ @Override - public void changeChannel() { + public CompletionStage changeChannel() { Channel currentChannel = channelSupport.getCurrentChannelInternal(); - if (currentChannel != null) { - currentChannel.getCurrentContextWithMetadata(contextType) - .thenAccept(resultOpt -> { - resultOpt.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); - }); + if (currentChannel == null) { + return CompletableFuture.completedFuture(null); } + return currentChannel.getCurrentContextWithMetadata(contextType) + .thenAccept(resultOpt -> { + resultOpt.ifPresent(cwm -> handler.handleContext(cwm.getContext(), cwm.getMetadata())); + }); } @Override 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 index a40ad426..3a702084 100644 --- 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 @@ -16,8 +16,8 @@ package org.finos.fdc3.proxy.channels; -import org.finos.fdc3.api.channel.Channel; -import org.finos.fdc3.api.types.Listener; +import java.util.concurrent.CompletionStage; + import org.finos.fdc3.proxy.listeners.RegisterableListener; /** @@ -25,12 +25,12 @@ * fdc3.addContextListener method. In this scenario, the listener will respond to broadcasts * on whatever is the current user channel. */ -public interface UserChannelContextListener extends Listener, RegisterableListener { +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. */ - void changeChannel(); + CompletionStage changeChannel(); } 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 index 2bcd0f74..dda62d47 100644 --- 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 @@ -155,7 +155,7 @@ public CompletionStage raiseIntent( @SuppressWarnings("unchecked") Map payloadMap = (Map) requestMap.get("payload"); if (payloadMap != null) { - payloadMap.put("metadata", ContextMetadataMapper.toWire(metadata)); + payloadMap.put("metadata", ContextMetadataMapper.toWireForIntentRequest(metadata, messaging::createUUID)); } return messaging.>exchange(requestMap, "raiseIntentResponse", appLaunchTimeout) @@ -223,7 +223,7 @@ public CompletionStage raiseIntentForContext( @SuppressWarnings("unchecked") Map raiseForContextPayload = (Map) requestMap.get("payload"); if (raiseForContextPayload != null) { - raiseForContextPayload.put("metadata", ContextMetadataMapper.toWire(metadata)); + raiseForContextPayload.put("metadata", ContextMetadataMapper.toWireForIntentRequest(metadata, messaging::createUUID)); } return messaging.>exchange(requestMap, "raiseIntentForContextResponse", appLaunchTimeout) 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 index f2e4744e..27432523 100644 --- 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 @@ -81,10 +81,15 @@ protected Map buildSubscribeRequest() { @Override public boolean filter(Map message) { + String messageType = (String) message.get("type"); + if (messageType == null) { + return false; + } if (eventType == null) { - return message.get("type") != null; + // Wildcard listeners receive agent events only, not request/response traffic. + return !messageType.endsWith("Response") && !messageType.endsWith("Request"); } - return getExpectedMessageType().equals(message.get("type")); + return getExpectedMessageType().equals(messageType); } /** @@ -121,7 +126,7 @@ public void action(Map message) { FDC3Event event = new FDC3Event(FDC3Event.Type.USER_CHANNEL_CHANGED, details); handler.handleEvent(event); } else { - handler.handleEvent(new FDC3Event(messageType, payload)); + handler.handleEvent(FDC3Event.fromWire(messageType, payload)); } } 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 index 70942275..5ff63391 100644 --- 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 @@ -20,9 +20,12 @@ 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; /** @@ -37,13 +40,60 @@ 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) { - return new LinkedHashMap<>((ContextMetadata) metadata); + 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)); } - throw new IllegalArgumentException("metadata must be ContextMetadata"); + + 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) { @@ -88,4 +138,23 @@ private static void applyMetaSourceIfAbsent(ContextMetadata metadata, 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/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/ChannelSteps.java index aa419ab3..6d663d24 100644 --- 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 @@ -27,6 +27,7 @@ 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; @@ -60,7 +61,7 @@ public void isAContext(String field, String 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.put("signature", ""); + metadata.setSignature(new DetachedSignature()); Map message = new HashMap<>(); message.put("type", "broadcastEvent"); @@ -76,11 +77,15 @@ public void isABroadcastEventMessage(String field, String channel, String contex @Given("{string} is a BroadcastEvent message on channel {string} with context {string} and metadata") public void isABroadcastEventMessageWithMetadata(String field, String channel, String contextType) { - ContextMetadata metadata = defaultBroadcastMetadata(); - metadata.put("signature", "test-sig"); - Map custom = new HashMap<>(); - custom.put("region", "EMEA"); - metadata.setCustom(custom); + 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"); @@ -417,7 +422,10 @@ public Object invoke(Object... args) throws Exception { // Handle CompletionStage/CompletableFuture if (result instanceof java.util.concurrent.CompletionStage) { - return ((java.util.concurrent.CompletionStage) result).toCompletableFuture().get(); + 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/Fdc3GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java index 7ae73d55..bc56e993 100644 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java +++ b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java @@ -642,7 +642,10 @@ private Object resolvePromise(Object promise) throws Exception { promise = ((Supplier) promise).get(); } if (promise instanceof CompletionStage) { - return ((CompletionStage) promise).toCompletableFuture().get(30, TimeUnit.SECONDS); + promise = ((CompletionStage) promise).toCompletableFuture().get(30, TimeUnit.SECONDS); + } + if (promise instanceof java.util.Optional) { + return ((java.util.Optional) promise).orElse(null); } return promise; } 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 index 1aee1181..35fa18ad 100644 --- 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 @@ -30,8 +30,8 @@ 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.metadata.DisplayMetadata; 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; @@ -133,38 +133,42 @@ public void raiseIntentReturnsContext(String result) { 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 raiseIntentReturnsContextWithMetadata( + public void raiseIntentReturnsContextWithMetadataAndAntiReplay( String result, String traceId, String signature, String antiReplayClaims) { TestMessaging.PossibleIntentResult intentResult = new TestMessaging.PossibleIntentResult(); intentResult.setContext((Context) handleResolve(result, world)); - ContextMetadata resultMetadata = new ContextMetadata(); - resultMetadata.setSource(new AppIdentifier("some-app", "abc123")); - resultMetadata.setTimestamp(Instant.parse("2024-01-01T00:00:00Z")); - resultMetadata.setTraceId(traceId); - resultMetadata.setSignature(new DetachedSignature( - signature + " (protected part)", - signature + " (signature part)")); - resultMetadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); - Map custom = new HashMap<>(); - custom.put("priority", "high"); - resultMetadata.setCustom(custom); - intentResult.setResultMetadata(resultMetadata); + 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 isMetadataWithTraceId(String field, String traceId, String signature, String antiReplayClaims) { - AppProvidableContextMetadata metadata = ContextMetadata.appProvidable(); + 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(new DetachedSignature( - signature + " (protected part)", - signature + " (signature part)")); - metadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); + metadata.setSignature(testDetachedSignature(signature)); + if (antiReplayClaims != null) { + metadata.setAntiReplay(ParseAntiReplayClaims.parse(antiReplayClaims)); + } Map custom = new HashMap<>(); custom.put("priority", "high"); metadata.setCustom(custom); - world.set(field, metadata); + return metadata; } @Given("Raise Intent will throw a {string} error") @@ -241,6 +245,13 @@ public void isAIntentEventMessage(String field, String intent, String context) { 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); @@ -375,5 +386,12 @@ private TestMessaging.IntentDetail createIntentDetail(AppIdentifier app, String 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/support/responses/ChannelStateResponse.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/support/responses/ChannelStateResponse.java index 384d14e2..5b63ddc6 100644 --- 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 @@ -240,7 +240,10 @@ private Map createGetContextResponse(Map message metadata.put("source", source); metadata.put("timestamp", java.time.Instant.now().toString()); metadata.put("traceId", "test-trace-id"); - metadata.put("signature", "test-signature"); + 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); 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 index 02314d13..5da42e88 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-channels.feature @@ -10,10 +10,10 @@ Feature: Channel Listeners Support Given "resultHandler" pipes context to "contexts" Scenario: Configuring two context listeners should mean they both pick up data - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -26,9 +26,9 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Unsubscribing a context listener prevents it collecting data. - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -40,9 +40,9 @@ Feature: Channel Listeners Support 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" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameter "{resultHandler}" + 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 @@ -56,9 +56,9 @@ Feature: Channel Listeners Support 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" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{resultHandler}" + 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 @@ -71,20 +71,20 @@ Feature: Channel Listeners Support | channel-name | {null} | addContextListenerRequest | Scenario: Passing invalid arguments to an app channel's addContextListener fn throws an error - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + 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" with parameters "{true}" and "{resultHandler}" + And I call "{channel1}" with "addContextListener" using arguments "{true}" and "{resultHandler}" Then "{result}" is an error - And I call "{channel1}" with "addContextListener" with parameters "{null}" and "{true}" + 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" with parameter "channel-name" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -95,21 +95,21 @@ Feature: Channel Listeners Support | channel-name | fdc3.instrument | addContextListenerRequest | Scenario: Destructured getCurrentContext after broadcast - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + 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" with parameter "{instrumentContext}" - And I call destructured "getCurrentContext" with parameter "fdc3.instrument" + 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" with parameter "channel-name" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -117,9 +117,9 @@ Feature: Channel Listeners Support Scenario: App channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api1}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api1}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "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 | 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 index 1a5cdbef..ebcc4f71 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/app-metadata.feature @@ -6,7 +6,7 @@ Feature: Desktop Agent Information And app "chipShop/c1" Scenario: Getting App metadata - When I call "{api}" with "getAppMetadata" with parameter "{c1}" + 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 | @@ -24,7 +24,7 @@ Feature: Desktop Agent Information | cucumber-app | cucumber-instance | Scenario: Getting instance information - When I call "{api}" with "findInstances" with parameter "{c1}" + When I call "{api}" with "findInstances" using argument "{c1}" Then "{result}" is an array of objects with the following contents | appId | instanceId | | One | 1 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature index 40f62599..889ceded 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/broadcast.feature @@ -9,21 +9,21 @@ Feature: Broadcasting Given "instrumentContext" is a "fdc3.instrument" context Scenario: Broadcasting on a named app channel - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" + 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" with parameter "{instrumentContext}" + 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" with parameter "one" - And I call "{api}" with "broadcast" with parameter "{instrumentContext}" + 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 | @@ -32,9 +32,9 @@ Feature: Broadcasting Scenario: Context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api}" with "getOrCreateChannel" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "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 | @@ -46,25 +46,25 @@ Feature: Broadcasting 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" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | custom.region | - | cucumber-app | cucumber-instance | test-sig | EMEA | + | 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" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "broadcast" with parameter "{instrumentContext}" - And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" + 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 | metadata.custom.key | - | fdc3.instrument | Apple | test-app | test-trace-id | test-signature | value | + | 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" with parameter "channel-name" + When I call "{api}" with "getOrCreateChannel" using argument "channel-name" And I refer to "{result}" as "channel1" - And I call "{channel1}" with "getCurrentContextWithMetadata" with parameter "fdc3.instrument" + 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 index d7fc546b..fce0f0aa 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/find-intents.feature @@ -13,7 +13,7 @@ Feature: Basic Intents Support And "crazyContext" is a "fdc3.unsupported" context Scenario: Find Intent can return the same intent with multiple apps - When I call "{api}" with "findIntent" with parameter "Buy" + When I call "{api}" with "findIntent" using argument "Buy" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -26,14 +26,14 @@ Feature: Basic Intents Support | Buy | findIntentRequest | Scenario: Find Intent can return an error when an intent doesn't match - When I call "{api}" with "findIntent" with parameter "Bob" + 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" with parameters "Buy" and "{instrumentContext}" + When I call "{api}" with "findIntent" using arguments "Buy" and "{instrumentContext}" Then "{result.intent}" is an object with the following contents | name | | Buy | @@ -45,7 +45,7 @@ Feature: Basic Intents Support | Buy | fdc3.instrument | AAPL | findIntentRequest | Scenario: Find Intent can filter by generic result type - When I call "{api}" with "findIntent" with parameters "OrderFood" and "{empty}" and "channel" + When I call "{api}" with "findIntent" using arguments "OrderFood", "{empty}", and "channel" Then "{result.intent}" is an object with the following contents | name | | OrderFood | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | OrderFood | channel | findIntentRequest | Scenario: Find Intents By Context - When I call "{api}" with "findIntentsByContext" with parameter "{instrumentContext}" + 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 | @@ -67,7 +67,7 @@ Feature: Basic Intents Support | 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" with parameter "{crazyContext}" + 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 | 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 index 798963fd..bca04b89 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-listener.feature @@ -8,7 +8,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Work Given "resultHandler" pipes intent to "intents" - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -19,7 +19,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Context) Given "resultHandler" returns a context item - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -27,7 +27,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return Results (Channel) Given "resultHandler" returns a channel - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | @@ -35,7 +35,7 @@ Feature: Intent Listeners Scenario: Intent Listeners Can Return A Void Result Given "resultHandler" returns a void promise - When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}" + 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 | 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 index eb24e57f..266232d0 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/intent-results.feature @@ -8,17 +8,17 @@ Feature: Intents Can Return Different Results Scenario: Raise Intent times out Given Raise Intent times out - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + 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" with parameters "OrderFood" and "{instrumentContext}" + 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -26,7 +26,7 @@ Feature: Intents Can Return Different Results | OrderFood | fdc3.instrument | AAPL | raiseIntentRequest | Scenario: Raising An intent With The App Parameter - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" + 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 | @@ -36,7 +36,7 @@ Feature: Intents Can Return Different Results Scenario: Context Data is returned in the result Given Raise Intent returns a context of "{instrumentContext}" - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | @@ -47,7 +47,7 @@ Feature: Intents Can Return Different Results Scenario: App Channel is returned in the result Given Raise Intent returns an app channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -58,7 +58,7 @@ Feature: Intents Can Return Different Results Scenario: User Channel is returned in the result Given Raise Intent returns a user channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -69,7 +69,7 @@ Feature: Intents Can Return Different Results Scenario: Private Channel is returned in the result Given Raise Intent returns a private channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | id | @@ -81,7 +81,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -93,7 +93,7 @@ Feature: Intents Can Return Different Results Scenario: Destructured raiseIntent with app parameter When I destructure method "raiseIntent" from "{api}" - And I call destructured "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" and "{c1}" + 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 | @@ -104,7 +104,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -117,7 +117,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{instrumentContext}" + 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 @@ -129,7 +129,7 @@ Feature: Intents Can Return Different Results Scenario: getResultMetadata returns DA-generated metadata for a context result Given Raise Intent returns a context of "{instrumentContext}" - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | @@ -137,7 +137,7 @@ Feature: Intents Can Return Different Results 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" with parameters "OrderFood" and "{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 | traceId | signature.signature | signature.protected | antiReplay.iat | antiReplay.exp | antiReplay.jti | @@ -145,7 +145,7 @@ Feature: Intents Can Return Different Results Scenario: getResultMetadata returns DA-generated metadata for a channel result Given Raise Intent returns a private channel - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | @@ -153,7 +153,7 @@ Feature: Intents Can Return Different Results Scenario: getResultMetadata returns DA-generated metadata for a void result Given Raise Intent returns no result - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{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 | diff --git a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature index cc57afab..5aa2c5a9 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/open.feature @@ -7,7 +7,7 @@ Feature: Desktop Agent Information And "instrumentContext" is a "fdc3.instrument" context Scenario: Open An App - When I call "{api}" with "open" with parameters "{c1}" and "{instrumentContext}" + When I call "{api}" with "open" using arguments "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -16,7 +16,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Open An App Using App ID - When I call "{api}" with "open" with parameters "chipShop" and "{instrumentContext}" + When I call "{api}" with "open" using arguments "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -25,7 +25,7 @@ Feature: Desktop Agent Information | chipShop | fdc3.instrument | AAPL | openRequest | Scenario: Opening a non-existent App - When I call "{api}" with "open" with parameters "nonExistent" and "{instrumentContext}" + 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 | @@ -33,7 +33,7 @@ Feature: Desktop Agent Information Scenario: Open An App - Destructured When I destructure method "open" from "{api}" - And I call destructured "open" with parameters "{c1}" and "{instrumentContext}" + And I call destructured "open" using arguments "{c1}" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -43,7 +43,7 @@ Feature: Desktop Agent Information Scenario: Open An App Using App ID - Destructured When I destructure method "open" from "{api}" - And I call destructured "open" with parameters "chipShop" and "{instrumentContext}" + And I call destructured "open" using arguments "chipShop" and "{instrumentContext}" Then "{result}" is an object with the following contents | appId | instanceId | | chipShop | abc123 | @@ -53,7 +53,7 @@ Feature: Desktop Agent Information 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" with parameters "{c1}" and "{null}" and "{openMetadata}" + 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 | 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 index d03f459d..0f36b274 100644 --- 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 @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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" @@ -22,7 +22,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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 @@ -31,7 +31,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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" @@ -43,7 +43,7 @@ Feature: Basic Private Channels Support 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" with parameter "{typesHandler}" + 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 @@ -52,7 +52,7 @@ Feature: Basic Private Channels Support 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" with parameter "{voidHandler}" + 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" @@ -64,7 +64,7 @@ Feature: Basic Private Channels Support 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" with parameter "{voidHandler}" + 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 index 8071b50d..e937fdac 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/private-channels.feature @@ -10,7 +10,7 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{contextHandler}" + 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 @@ -20,7 +20,7 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -28,7 +28,7 @@ Feature: Basic Private Channels Support Scenario: Private channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{privateChannel}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -39,7 +39,7 @@ Feature: Basic Private Channels Support 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" with parameters "addContextListener" and "{typesHandler}" + 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" @@ -51,7 +51,7 @@ Feature: Basic Private Channels Support 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" with parameters "addContextListener" and "{typesHandler}" + 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 @@ -60,7 +60,7 @@ Feature: Basic Private Channels Support 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" with parameters "unsubscribe" and "{typesHandler}" + 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" @@ -72,7 +72,7 @@ Feature: Basic Private Channels Support 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" with parameters "unsubscribe" and "{typesHandler}" + 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 @@ -84,7 +84,7 @@ Feature: Basic Private Channels Support 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" with parameters "{null}" and "{typesHandler}" + 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}" @@ -93,7 +93,7 @@ Feature: Basic Private Channels Support 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" with parameters "disconnect" and "{voidHandler}" + 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" @@ -105,14 +105,14 @@ Feature: Basic Private Channels Support 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" with parameters "disconnect" and "{voidHandler}" + 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" with parameter "{instrumentContext}" + 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 | @@ -135,8 +135,8 @@ Feature: Basic Private Channels Support 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" with parameters "fdc3.instrument" and "{resultHandler}" - And I call destructured "broadcast" with parameter "{instrumentContext}" + 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 | 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 index bc773f22..d79bbc1f 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/raise-intents.feature @@ -18,7 +18,7 @@ Feature: Basic Intents Support The intent resolver will just take the first matching application that would resolve the intent. - When I call "{api}" with "raiseIntent" with parameters "OrderFood" and "{instrumentContext}" + 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 | @@ -28,14 +28,14 @@ Feature: Basic Intents Support | 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" with parameters "OrderFood" and "{cancelContext}" + 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" with parameters "Buy" and "{instrumentContext}" + 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 | @@ -47,7 +47,7 @@ Feature: Basic Intents Support The intent resolver will just take the first matching application that would resolve an intent. - When I call "{api}" with "raiseIntentForContext" with parameter "{instrumentContext}" + When I call "{api}" with "raiseIntentForContext" using argument "{instrumentContext}" Then "{result}" is an object with the following contents | source.appId | source.instanceId | | chipShop | c1 | @@ -57,7 +57,7 @@ Feature: Basic Intents Support | fdc3.instrument | AAPL | c1 | raiseIntentRequest | Scenario: Raising Intent By Context exactly right, so the resolver isn't required - When I call "{api}" with "raiseIntentForContext" with parameters "{countryContext}" and "{t1}" + 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 | @@ -66,7 +66,7 @@ Feature: Basic Intents Support | 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" with parameter "{cancelContext}" + 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 | @@ -74,7 +74,7 @@ Feature: Basic Intents Support 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" with parameters "Buy" and "{instrumentContext}" and "{null}" and "{intentMetadata}" + 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 | @@ -83,14 +83,14 @@ Feature: Basic Intents Support | 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" with parameters "Buy" and "{instrumentContext}" + 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" with parameters "{countryContext}" and "{null}" and "{intentMetadata}" + 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 | 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 index 845e0a3d..ca11b6a1 100644 --- 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 @@ -11,14 +11,14 @@ Feature: Updating User Channel State Scenario: Joining A User Channel Receives Correct Context on Listener Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "one" + 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" with parameter "fdc3.instrument" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" Then "{result}" is an object with the following contents | type | name | | fdc3.instrument | Apple | @@ -31,12 +31,12 @@ Feature: Updating User Channel State Scenario: Changing User Channel Doesn't Receive Incorrect Context on Listener Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" - When I call "{api}" with "joinUserChannel" with parameter "two" + 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" with parameter "fdc3.instrument" + And I call "{result}" with "getCurrentContext" using argument "fdc3.instrument" Then "{result}" is null Scenario: disconnection 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 index 367fcbad..c74bb6bc 100644 --- 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 @@ -16,7 +16,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel Scenario: Adding a Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -28,7 +28,7 @@ Feature: User Channels Support where the Desktop Agent puts the app on a channel 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | 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 index 2e5fdca7..d0e4d9cf 100644 --- a/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature +++ b/fdc3-agent-proxy/src/test/resources/temporary-features/user-channels.feature @@ -51,7 +51,7 @@ Feature: Basic User Channels Support Scenario: Changing Channel You should be able to join a channel knowing it's ID. - When I call "{api}" with "joinUserChannel" with parameter "one" + 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 | @@ -64,7 +64,7 @@ Feature: Basic User Channels Support Scenario: Changing Channel via Deprecated API You should be able to join a channel knowing it's ID. - When I call "{api}" with "joinChannel" with parameter "one" + 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 | @@ -76,8 +76,8 @@ Feature: Basic User Channels Support Scenario: Adding a Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -91,8 +91,8 @@ Feature: Basic User Channels Support Scenario: Adding an Un-Typed Listener on a given User Channel Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "{empty}" and "{resultHandler}" + 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 | @@ -106,8 +106,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameter "{resultHandler}" + 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 | @@ -121,7 +121,7 @@ Feature: Basic User Channels Support Scenario: If you haven't joined a channel, your listener receives nothing Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -130,8 +130,8 @@ Feature: Basic User Channels Support Scenario: After unsubscribing, my listener shouldn't receive any more messages Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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" @@ -149,8 +149,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -164,24 +164,24 @@ Feature: Basic User Channels Support | id.ticker | type | name | Scenario: Joining a user channel that doesn't exist throws an error - When I call "{api}" with "joinUserChannel" with parameter "nonexistent" + 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" with parameters "{true}" and "{resultHandler}" + 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" with parameters "{null}" and "{true}" + 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" with parameter "one" + 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" with parameter "{instrumentContext}" + 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 | @@ -195,17 +195,17 @@ Feature: Basic User Channels Support 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" with parameter "one" + 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" with parameter "fdc3.email" + 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" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 @@ -216,7 +216,7 @@ Feature: Basic User Channels Support | 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" with parameter "one" + 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 @@ -224,7 +224,7 @@ Feature: Basic User Channels Support Scenario: Adding and removing A User Channel Changed Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "userChannelChanged" and "{typesHandler}" + 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}" @@ -244,7 +244,7 @@ Feature: Basic User Channels Support Scenario: Adding and removing A "null" (i.e. wildcard) Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "{null}" and "{typesHandler}" + 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}" @@ -264,13 +264,13 @@ Feature: Basic User Channels Support Scenario: Adding An Unknown Event Listener Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "unknownEventType" and "{typesHandler}" + 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" with parameters "userChannelChanged" and "{typesHandler}" + 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 @@ -280,7 +280,7 @@ Feature: Basic User Channels Support 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" with parameters "userChannelChanged" and "{typesHandler}" + 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 @@ -290,7 +290,7 @@ Feature: Basic User Channels Support 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" with parameters "userChannelChanged" and "{typesHandler}" + 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 @@ -300,7 +300,7 @@ Feature: Basic User Channels Support 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" with parameters "userChannelChanged" and "{typesHandler}" + 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 @@ -309,7 +309,7 @@ Feature: Basic User Channels Support Scenario: Wildcard event listener fires and forwards non-channelChangedEvent messages Given "typesHandler" pipes events to "types" - When I call "{api}" with "addEventListener" with parameters "{null}" and "{typesHandler}" + 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 | @@ -326,7 +326,7 @@ Feature: Basic User Channels Support Scenario: Destructured joinUserChannel and getCurrentChannel work correctly When I destructure method "joinUserChannel" from "{api}" - And I call destructured "joinUserChannel" with parameter "one" + 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 @@ -339,12 +339,12 @@ Feature: Basic User Channels Support Scenario: Destructured channel getCurrentContext after broadcast Given "resultHandler" pipes context to "contexts" - When I call "{api}" with "joinUserChannel" with parameter "one" + 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" with parameter "{instrumentContext}" + 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 | @@ -353,8 +353,8 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call destructured "broadcast" with parameter "{instrumentContext}" + 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" @@ -365,13 +365,13 @@ Feature: Basic User Channels Support 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" with parameter "one" - And I call destructured "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | @@ -382,8 +382,8 @@ Feature: Basic User Channels Support Scenario: User channel context listener receives source metadata Given "resultHandler" pipes context and metadata to "contexts" and "metadatas" - When I call "{api}" with "joinUserChannel" with parameter "one" - And I call "{api}" with "addContextListener" with parameters "fdc3.instrument" and "{resultHandler}" + 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 | diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java index f25f455e..0f546fe3 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java @@ -67,24 +67,44 @@ public String toString() { } private final Type type; + private final String wireType; private final Object details; public FDC3Event(Type type, Object details) { this.type = type; + this.wireType = null; + this.details = details; + } + + private FDC3Event(String wireType, Object details) { + this.type = null; + this.wireType = wireType; this.details = details; } /** - * Returns the type of the event. + * Creates an event forwarded from a DACP wire message (for wildcard listeners). + */ + public static FDC3Event fromWire(String wireMessageType, Object details) { + return new FDC3Event(wireMessageType, details); + } + + /** + * Returns the typed enum constant for this event, or {@code null} for wire-forwarded events. */ public Type getType() { return type; } + /** + * Returns the event type as a string (API or wire message type). + */ + public String getTypeString() { + return type != null ? type.getValue() : wireType; + } + /** * Returns the details object containing additional information about the event. - * - * @return the event details */ public Object getDetails() { return details; From 52fad2e8173bb0c1889fb4841a90a585da1cbb64 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Sun, 7 Jun 2026 12:22:05 +0100 Subject: [PATCH 64/65] Fixed FDC3Event construction --- .../listeners/DesktopAgentEventListener.java | 16 +++--- .../org/finos/fdc3/api/types/FDC3Event.java | 53 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) 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 index 27432523..4eb7d445 100644 --- 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 @@ -113,8 +113,13 @@ private String getExpectedMessageType() { public void action(Map message) { String messageType = (String) message.get("type"); Map payload = (Map) message.get("payload"); - - // Restructure the event based on message type + + 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"); @@ -122,12 +127,9 @@ public void action(Map message) { currentChannelId = payload.get("newChannelId"); } details.put("currentChannelId", currentChannelId); - - FDC3Event event = new FDC3Event(FDC3Event.Type.USER_CHANNEL_CHANGED, details); - handler.handleEvent(event); - } else { - handler.handleEvent(FDC3Event.fromWire(messageType, payload)); + return details; } + return payload; } private FDC3EventType toFDC3SchemaEventType(String eventType) { diff --git a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java index 0f546fe3..7a46cae6 100644 --- a/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java +++ b/fdc3-standard/src/main/java/org/finos/fdc3/api/types/FDC3Event.java @@ -17,6 +17,7 @@ package org.finos.fdc3.api.types; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; /** @@ -60,6 +61,30 @@ public static Type fromValue(String value) { throw new IllegalArgumentException("Unknown FDC3 event type: " + value); } + /** + * Maps a DACP agent event message type to the corresponding API event type, + * or {@code null} if the message is not a recognised FDC3 event. + */ + public static Type fromMessageType(String messageType) { + if (messageType == null) { + return null; + } + switch (messageType) { + case "channelChangedEvent": + return USER_CHANNEL_CHANGED; + case "contextClearedEvent": + return CONTEXT_CLEARED; + case "privateChannelOnAddContextListenerEvent": + return ADD_CONTEXT_LISTENER; + case "privateChannelOnUnsubscribeEvent": + return ON_UNSUBSCRIBE; + case "privateChannelOnDisconnectEvent": + return ON_DISCONNECT; + default: + return null; + } + } + @Override public String toString() { return value; @@ -67,42 +92,22 @@ public String toString() { } private final Type type; - private final String wireType; private final Object details; - public FDC3Event(Type type, Object details) { + @JsonCreator + public FDC3Event(@JsonProperty("type") Type type, @JsonProperty("details") Object details) { this.type = type; - this.wireType = null; - this.details = details; - } - - private FDC3Event(String wireType, Object details) { - this.type = null; - this.wireType = wireType; this.details = details; } /** - * Creates an event forwarded from a DACP wire message (for wildcard listeners). - */ - public static FDC3Event fromWire(String wireMessageType, Object details) { - return new FDC3Event(wireMessageType, details); - } - - /** - * Returns the typed enum constant for this event, or {@code null} for wire-forwarded events. + * Returns the typed enum constant for this event, or {@code null} for + * wildcard listeners receiving unmapped agent events. */ public Type getType() { return type; } - /** - * Returns the event type as a string (API or wire message type). - */ - public String getTypeString() { - return type != null ? type.getValue() : wireType; - } - /** * Returns the details object containing additional information about the event. */ From 4fb16d589d1f6d4309ac33a5a87e57c1068d3872 Mon Sep 17 00:00:00 2001 From: Rob Moffat Date: Mon, 8 Jun 2026 11:21:37 +0100 Subject: [PATCH 65/65] FIxed all tests, now 3.0.0 / WSCP compatible. --- .../org/finos/fdc3/proxy/RunCucumberTest.java | 2 +- .../finos/fdc3/proxy/TestSpringConfig.java | 3 +- .../finos/fdc3/proxy/steps/ChannelSteps.java | 2 +- .../fdc3/proxy/steps/Fdc3GenericSteps.java | 820 ------------------ .../finos/fdc3/proxy/world/CustomWorld.java | 27 + fdc3-context/pom.xml | 53 +- .../org/finos/fdc3/example/ExampleApp.java | 120 +-- fdc3-get-agent/pom.xml | 70 +- .../org/finos/fdc3/getagent/GetAgent.java | 170 ++-- .../finos/fdc3/getagent/GetAgentParams.java | 192 ++-- .../fdc3/getagent/WebSocketMessaging.java | 33 +- .../finos/fdc3/getagent/RunCucumberTest.java | 41 + .../finos/fdc3/getagent/TestSpringConfig.java | 10 +- .../fdc3/getagent/steps/GetAgentSteps.java | 116 +++ .../getagent/support/MockWebSocketServer.java | 194 +++-- .../test/resources/features/get-agent.feature | 71 +- fdc3-schema/pom.xml | 10 +- .../3.0.0/api/WSCP1ConnectRequest.schema.json | 84 ++ .../WSCP2ConnectFailedResponse.schema.json | 47 + .../api/WSCP2ConnectResponse.schema.json | 68 ++ .../3.0.0/api/WSCP3Goodbye.schema.json | 31 + .../3.0.0/api/WSCPConnectionStep.schema.json | 73 ++ pom.xml | 14 + 23 files changed, 994 insertions(+), 1257 deletions(-) delete mode 100644 fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java create mode 100644 fdc3-get-agent/src/test/java/org/finos/fdc3/getagent/RunCucumberTest.java create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP1ConnectRequest.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectFailedResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP2ConnectResponse.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCP3Goodbye.schema.json create mode 100644 fdc3-schema/src/main/schemas-temp/3.0.0/api/WSCPConnectionStep.schema.json 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 index 04358f37..05cd3ef8 100644 --- 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 @@ -35,7 +35,7 @@ @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") + 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, 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 index 8dec2e2e..b586b3bb 100644 --- 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 @@ -35,7 +35,8 @@ */ @Configuration @ComponentScan(basePackages = { - "org.finos.fdc3.proxy.steps" + "org.finos.fdc3.proxy.steps", + "io.github.robmoffat.steps" }) public class TestSpringConfig { 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 index 6d663d24..f42048f0 100644 --- 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 @@ -412,7 +412,7 @@ public DestructuredMethod(Object target, String methodName) { } public Object invoke(Object... args) throws Exception { - java.lang.reflect.Method method = Fdc3GenericSteps.findMethod(target.getClass(), methodName, args); + 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); } diff --git a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java b/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java deleted file mode 100644 index bc56e993..00000000 --- a/fdc3-agent-proxy/src/test/java/org/finos/fdc3/proxy/steps/Fdc3GenericSteps.java +++ /dev/null @@ -1,820 +0,0 @@ -/** - * 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.doesRowMatch; -import static io.github.robmoffat.support.MatchingUtils.handleResolve; -import static io.github.robmoffat.support.MatchingUtils.matchData; -import static io.github.robmoffat.support.MatchingUtils.matchDataAtLeast; -import static io.github.robmoffat.support.MatchingUtils.matchDataDoesntContain; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -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.TimeUnit; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.finos.fdc3.api.types.EventHandler; - -import io.github.robmoffat.world.PropsWorld; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; - -/** - * Generic Cucumber step definitions (local copy of standard-cucumber-steps with FDC3-specific adjustments). - */ -public class Fdc3GenericSteps { - - private final PropsWorld world; - private final Map> jobs = new HashMap<>(); - - public Fdc3GenericSteps(PropsWorld world) { - this.world = world; - } - - // Functional interfaces for multi-arg functions - @FunctionalInterface - public interface ThreeArgFunction { - CompletableFuture apply(Object a, Object b, Object c); - } - - @FunctionalInterface - public interface FourArgFunction { - CompletableFuture apply(Object a, Object b, Object c, Object d); - } - - // ========== Method Invocation (Object.method) Steps ========== - - @When("I call {string} with {string}") - public void iCallWith(String field, String fnName) { - try { - Object object = handleResolve(field, world); - Object result = invokeMethod(object, fnName); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} using argument {string}") - public void iCallWithArgument(String field, String fnName, String param) { - try { - Object object = handleResolve(field, world); - Object paramValue = handleResolve(param, world); - Object result = invokeMethod(object, fnName, paramValue); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} using arguments {string} and {string}") - public void iCallWithTwoArguments(String field, String fnName, String param1, String param2) { - try { - Object object = handleResolve(field, world); - Object result = invokeMethod(object, fnName, handleResolve(param1, world), handleResolve(param2, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} using arguments {string}, {string}, and {string}") - public void iCallWithThreeArguments(String field, String fnName, String param1, String param2, String param3) { - try { - Object object = handleResolve(field, world); - Object result = invokeMethod(object, fnName, - handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} using arguments {string}, {string}, {string}, and {string}") - public void iCallWithFourArguments(String field, String fnName, String param1, String param2, String param3, String param4) { - try { - Object object = handleResolve(field, world); - Object result = invokeMethod(object, fnName, - handleResolve(param1, world), handleResolve(param2, world), - handleResolve(param3, world), handleResolve(param4, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} with {string} as {string}") - public void startMethodJob(String field, String fnName, String jobName) { - startMethodJobWithArgs(field, fnName, jobName); - } - - @When("I call {string} with {string} using argument {string} as {string}") - public void startMethodJobWithArgument(String field, String fnName, String param, String jobName) { - startMethodJobWithArgs(field, fnName, jobName, handleResolve(param, world)); - } - - @When("I call {string} with {string} using arguments {string} and {string} as {string}") - public void startMethodJobWithTwoArguments(String field, String fnName, String param1, String param2, String jobName) { - startMethodJobWithArgs(field, fnName, jobName, handleResolve(param1, world), handleResolve(param2, world)); - } - - @When("I call {string} with {string} using arguments {string}, {string}, and {string} as {string}") - public void startMethodJobWithThreeArguments(String field, String fnName, String param1, String param2, String param3, String jobName) { - startMethodJobWithArgs(field, fnName, jobName, - handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); - } - - @When("I call {string} with {string} using arguments {string}, {string}, {string}, and {string} as {string}") - public void startMethodJobWithFourArguments(String field, String fnName, String param1, String param2, String param3, String param4, String jobName) { - startMethodJobWithArgs(field, fnName, jobName, - handleResolve(param1, world), handleResolve(param2, world), - handleResolve(param3, world), handleResolve(param4, world)); - } - - private void startMethodJobWithArgs(String field, String fnName, String jobName, Object... args) { - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object object = handleResolve(field, world); - return invokeMethod(object, fnName, args); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - // ========== Direct Function Call Steps ========== - - @When("I call {string}") - public void iCallFunction(String fnName) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctional(fn); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} using argument {string}") - public void iCallFunctionWithArgument(String fnName, String param) { - try { - Object fn = handleResolve(fnName, world); - Object paramVal = handleResolve(param, world); - Object result = callFunctionalWithArgs(fn, paramVal); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} using arguments {string} and {string}") - public void iCallFunctionWithTwoArguments(String fnName, String param1, String param2) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, handleResolve(param1, world), handleResolve(param2, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} using arguments {string}, {string}, and {string}") - public void iCallFunctionWithThreeArguments(String fnName, String param1, String param2, String param3) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, - handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - @When("I call {string} using arguments {string}, {string}, {string}, and {string}") - public void iCallFunctionWithFourArguments(String fnName, String param1, String param2, String param3, String param4) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, - handleResolve(param1, world), handleResolve(param2, world), - handleResolve(param3, world), handleResolve(param4, world)); - world.set("result", result); - } catch (Exception error) { - world.set("result", error); - } - } - - // ========== Variable Reference ========== - - @When("I refer to {string} as {string}") - public void iReferToAs(String from, String to) { - world.set(to, handleResolve(from, world)); - } - - // ========== Array Matching Steps ========== - - @Then("{string} is an array of objects with the following contents") - public void isAnArrayOfObjectsWithContents(String field, DataTable dt) { - matchData(world, toList(handleResolve(field, world)), dt); - } - - @Then("{string} is an array of objects with at least the following contents") - public void isAnArrayOfObjectsWithAtLeastContents(String field, DataTable dt) { - matchDataAtLeast(world, toList(handleResolve(field, world)), dt); - } - - @Then("{string} is an array of objects which doesn't contain any of") - public void isAnArrayOfObjectsWhichDoesntContainAnyOf(String field, DataTable dt) { - matchDataDoesntContain(world, toList(handleResolve(field, world)), dt); - } - - @Then("{string} is an array of objects with length {string}") - public void isAnArrayOfObjectsWithLength(String field, String lengthField) { - List data = toList(handleResolve(field, world)); - Object resolved = handleResolve(lengthField, world); - assertEquals(Integer.parseInt(String.valueOf(resolved)), data.size()); - } - - @Then("{string} is an array of strings with the following values") - public void isAnArrayOfStringsWithValues(String field, DataTable dt) { - List data = toList(handleResolve(field, world)); - List> values = data.stream() - .map(s -> Map.of("value", s)) - .collect(Collectors.toList()); - matchData(world, values, dt); - } - - @Then("{string} is an object with the following contents") - public void isAnObjectWithContents(String field, DataTable params) { - List> table = params.asMaps(); - Object data = handleResolve(field, world); - assertTrue(doesRowMatch(world, table.get(0), data)); - } - - // ========== Value Assertions ========== - - @Then("{string} is null") - public void isNull(String field) { - assertNull(handleResolve(field, world)); - } - - @Then("{string} is not null") - public void isNotNull(String field) { - assertNotNull(handleResolve(field, world)); - } - - @Then("{string} is true") - public void isTrue(String field) { - assertTrue(isTruthy(handleResolve(field, world))); - } - - @Then("{string} is false") - public void isFalse(String field) { - assertFalse(isTruthy(handleResolve(field, world))); - } - - @Then("{string} is undefined") - public void isUndefined(String field) { - assertNull(handleResolve(field, world)); - } - - @Then("{string} is empty") - public void isEmpty(String field) { - Object data = handleResolve(field, world); - if (data instanceof List) { - assertTrue(((List) data).isEmpty()); - } else if (data instanceof String) { - assertTrue(((String) data).isEmpty()); - } else if (data == null) { - // null is considered empty - } else { - throw new AssertionError("Expected empty collection or string, got: " + data.getClass().getName()); - } - } - - // ========== Error Assertions ========== - - @Then("{string} is an error with message {string}") - public void isAnErrorWithMessage(String field, String errorType) { - Object value = handleResolve(field, world); - assertTrue(value instanceof Throwable, "Expected a Throwable but got: " + value); - Throwable t = (Throwable) value; - String message = getRootCauseMessage(t); - assertEquals(errorType, message); - } - - @Then("{string} is an error") - public void isAnError(String field) { - assertTrue(handleResolve(field, world) instanceof Throwable); - } - - @Then("{string} is not an error") - public void isNotAnError(String field) { - assertFalse(handleResolve(field, world) instanceof Throwable); - } - - @Then("{string} contains {string}") - public void contains(String field, String substring) { - String actual = String.valueOf(handleResolve(field, world)); - assertTrue(actual.contains(substring), "Expected '" + actual + "' to contain '" + substring + "'"); - } - - @Then("{string} is a string containing one of") - public void isAStringContainingOneOf(String field, DataTable dt) { - String actual = String.valueOf(handleResolve(field, world)); - List> rows = dt.cells(); - List values = rows.stream().skip(1).map(r -> r.get(0)).collect(Collectors.toList()); - boolean found = values.stream().anyMatch(actual::contains); - assertTrue(found, "Expected '" + actual + "' to contain one of: " + values); - } - - @Then("{string} should be greater than {string}") - public void shouldBeGreaterThan(String field, String threshold) { - double actual = toDouble(handleResolve(field, world)); - double thresh = toDouble(handleResolve(threshold, world)); - assertTrue(actual > thresh, "Expected " + actual + " > " + thresh); - } - - @Then("{string} should be less than {string}") - public void shouldBeLessThan(String field, String threshold) { - double actual = toDouble(handleResolve(field, world)); - double thresh = toDouble(handleResolve(threshold, world)); - assertTrue(actual < thresh, "Expected " + actual + " < " + thresh); - } - - // ========== Test Setup ========== - - @Given("{string} is a invocation counter into {string}") - public void isAnInvocationCounter(String handlerName, String counterField) { - world.set(counterField, 0); - world.set(handlerName, (EventHandler) event -> { - int amount = (Integer) world.get(counterField); - world.set(counterField, amount + 1); - }); - } - - @Given("{string} is an async function returning {string}") - public void isAnAsyncFunctionReturning(String fnName, String valueField) { - Object value = handleResolve(valueField, world); - world.set(fnName, (Supplier>) () -> - CompletableFuture.completedFuture(value)); - } - - @Given("{string} is an async function returning {string} after {string} ms") - public void isAnAsyncFunctionReturningAfterDelay(String fnName, String valueField, String delayMs) { - Object value = handleResolve(valueField, world); - long delay = Long.parseLong(delayMs); - world.set(fnName, (Supplier>) () -> - CompletableFuture.supplyAsync(() -> { - try { - Thread.sleep(delay); - return value; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - })); - } - - // Setter step: I set "field" to "value" - @Given("I set {string} to {string}") - public void iSetFieldTo(String field, String value) { - world.set(field, handleResolve(value, world)); - } - - // Assertion step: "{field}" is "value" - @Then("{string} is {string}") - public void fieldIs(String field, String value) { - Object actual = handleResolve(field, world); - Object expected = handleResolve(value, world); - assertEquals(String.valueOf(expected), String.valueOf(actual)); - } - - @Given("we wait for a period of {string} ms") - public void weWaitForPeriod(String ms) throws InterruptedException { - Thread.sleep(Long.parseLong(ms)); - } - - // ========== Async Job Steps ========== - - @When("I start {string} as {string}") - public void startJob(String fnName, String jobName) { - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object fn = handleResolve(fnName, world); - return callFunctional(fn); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - @When("I start {string} using argument {string} as {string}") - public void startJobWithArgument(String fnName, String param, String jobName) { - Object paramVal = handleResolve(param, world); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object fn = handleResolve(fnName, world); - return callFunctionalWithArgs(fn, paramVal); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - @When("I start {string} using arguments {string} and {string} as {string}") - public void startJobWithTwoArguments(String fnName, String param1, String param2, String jobName) { - Object p1 = handleResolve(param1, world); - Object p2 = handleResolve(param2, world); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object fn = handleResolve(fnName, world); - return callFunctionalWithArgs(fn, p1, p2); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - @When("I start {string} using arguments {string}, {string}, and {string} as {string}") - public void startJobWithThreeArguments(String fnName, String param1, String param2, String param3, String jobName) { - Object p1 = handleResolve(param1, world); - Object p2 = handleResolve(param2, world); - Object p3 = handleResolve(param3, world); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object fn = handleResolve(fnName, world); - return callFunctionalWithArgs(fn, p1, p2, p3); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - @When("I start {string} using arguments {string}, {string}, {string}, and {string} as {string}") - public void startJobWithFourArguments(String fnName, String param1, String param2, String param3, String param4, String jobName) { - Object p1 = handleResolve(param1, world); - Object p2 = handleResolve(param2, world); - Object p3 = handleResolve(param3, world); - Object p4 = handleResolve(param4, world); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - Object fn = handleResolve(fnName, world); - return callFunctionalWithArgs(fn, p1, p2, p3, p4); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - jobs.put(jobName, future); - } - - @Then("I wait for job {string}") - public void waitForJob(String jobName) { - try { - Object result = jobs.get(jobName).get(30, TimeUnit.SECONDS); - world.set("result", result); - world.set(jobName, result); - } catch (Exception e) { - world.set("result", e); - world.set(jobName, e); - } - } - - @Then("I wait for job {string} within {string} ms") - public void waitForJobWithTimeout(String jobName, String timeoutMs) { - try { - long ms = Long.parseLong(timeoutMs); - Object result = jobs.get(jobName).get(ms, TimeUnit.MILLISECONDS); - world.set("result", result); - world.set(jobName, result); - } catch (Exception e) { - world.set("result", e); - world.set(jobName, e); - } - } - - @When("I wait for {string}") - public void iWaitFor(String fnName) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctional(fn); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - @When("I wait for {string} within {string} ms") - public void iWaitForWithTimeout(String fnName, String timeoutMs) { - try { - Object fn = handleResolve(fnName, world); - long ms = Long.parseLong(timeoutMs); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - return callFunctional(fn); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - Object result = future.get(ms, TimeUnit.MILLISECONDS); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - @When("I wait for {string} using argument {string}") - public void iWaitForWithArgument(String fnName, String param) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, handleResolve(param, world)); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - @When("I wait for {string} using arguments {string} and {string}") - public void iWaitForWithTwoArguments(String fnName, String param1, String param2) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, handleResolve(param1, world), handleResolve(param2, world)); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - @When("I wait for {string} using arguments {string}, {string}, and {string}") - public void iWaitForWithThreeArguments(String fnName, String param1, String param2, String param3) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, - handleResolve(param1, world), handleResolve(param2, world), handleResolve(param3, world)); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - @When("I wait for {string} using arguments {string}, {string}, {string}, and {string}") - public void iWaitForWithFourArguments(String fnName, String param1, String param2, String param3, String param4) { - try { - Object fn = handleResolve(fnName, world); - Object result = callFunctionalWithArgs(fn, - handleResolve(param1, world), handleResolve(param2, world), - handleResolve(param3, world), handleResolve(param4, world)); - world.set("result", result); - } catch (Exception e) { - world.set("result", e); - } - } - - // ========== Helper Methods ========== - - private Object callFunctional(Object fn) throws Exception { - if (fn instanceof Runnable) { - ((Runnable) fn).run(); - return null; - } - if (fn instanceof java.util.concurrent.Callable) { - Object result = ((java.util.concurrent.Callable) fn).call(); - return resolvePromise(result); - } - if (fn instanceof Supplier) { - Object result = ((Supplier) fn).get(); - return resolvePromise(result); - } - throw new IllegalArgumentException("Not a callable: " + (fn == null ? "null" : fn.getClass().getName())); - } - - private Object callFunctionalWithArgs(Object fn, Object... args) throws Exception { - Method invokeMethod = findMethod(fn.getClass(), "apply", args); - if (invokeMethod == null) { - invokeMethod = findMethod(fn.getClass(), "accept", args); - } - if (invokeMethod == null) { - invokeMethod = findMethod(fn.getClass(), "call", args); - } - if (invokeMethod != null) { - invokeMethod.setAccessible(true); - Object result = invokeMethod.invoke(fn, args); - return resolvePromise(result); - } - throw new IllegalArgumentException("Cannot call " + fn.getClass().getName() + " with " + args.length + " args"); - } - - private Object resolvePromise(Object promise) throws Exception { - if (promise instanceof Supplier) { - promise = ((Supplier) promise).get(); - } - if (promise instanceof CompletionStage) { - promise = ((CompletionStage) promise).toCompletableFuture().get(30, TimeUnit.SECONDS); - } - if (promise instanceof java.util.Optional) { - return ((java.util.Optional) promise).orElse(null); - } - return promise; - } - - private Object invokeMethod(Object target, String methodName, Object... args) throws Exception { - Method method = findMethod(target.getClass(), methodName, args); - if (method == null) { - throw new NoSuchMethodException("Method not found: " + methodName); - } - method.setAccessible(true); - // Convert args to match parameter types - Object[] convertedArgs = convertArgs(method.getParameterTypes(), args); - Object result = method.invoke(target, convertedArgs); - return resolvePromise(result); - } - - private Object[] convertArgs(Class[] paramTypes, Object[] args) { - Object[] converted = new Object[args.length]; - for (int i = 0; i < args.length; i++) { - converted[i] = convertArg(paramTypes[i], args[i]); - } - return converted; - } - - private Object convertArg(Class targetType, Object arg) { - if (arg == null) return null; - - // If already compatible, return as-is - if (wrap(targetType).isAssignableFrom(arg.getClass())) { - return arg; - } - - // Handle numeric conversions - Number num = null; - if (arg instanceof Number) { - num = (Number) arg; - } else if (arg instanceof String) { - try { - num = Double.parseDouble((String) arg); - } catch (NumberFormatException e) { - // Not a number, check for char - if ((targetType == char.class || targetType == Character.class) && ((String) arg).length() == 1) { - return ((String) arg).charAt(0); - } - return arg; - } - } - - if (num != null) { - if (targetType == int.class || targetType == Integer.class) return num.intValue(); - if (targetType == long.class || targetType == Long.class) return num.longValue(); - if (targetType == double.class || targetType == Double.class) return num.doubleValue(); - if (targetType == float.class || targetType == Float.class) return num.floatValue(); - if (targetType == short.class || targetType == Short.class) return num.shortValue(); - if (targetType == byte.class || targetType == Byte.class) return num.byteValue(); - } - - return arg; - } - - public static Method findMethod(Class targetClass, String name, Object... args) { - Method bestMatch = null; - for (Method method : targetClass.getMethods()) { - if (!method.getName().equals(name)) continue; - Class[] paramTypes = method.getParameterTypes(); - if (paramTypes.length != args.length) continue; - if (isCompatible(paramTypes, args)) { - if (bestMatch == null || isMoreSpecific(paramTypes, bestMatch.getParameterTypes())) { - bestMatch = method; - } - } - } - return bestMatch; - } - - private static boolean isCompatible(Class[] paramTypes, Object[] args) { - for (int i = 0; i < paramTypes.length; i++) { - if (args[i] == null) { - if (paramTypes[i].isPrimitive()) return false; - continue; - } - Class wrapped = wrap(paramTypes[i]); - Class argClass = args[i].getClass(); - // Direct assignment compatibility - if (wrapped.isAssignableFrom(argClass)) continue; - // Handle numeric conversions: any Number can be converted to any numeric primitive - if (args[i] instanceof Number && isNumericType(paramTypes[i])) continue; - // Handle String to numeric conversion - if (args[i] instanceof String && isNumericType(paramTypes[i])) { - try { - Double.parseDouble((String) args[i]); - continue; - } catch (NumberFormatException e) { - return false; - } - } - // Handle String to char conversion - if (args[i] instanceof String && (paramTypes[i] == char.class || paramTypes[i] == Character.class)) { - if (((String) args[i]).length() == 1) continue; - } - return false; - } - return true; - } - - private static boolean isNumericType(Class type) { - return type == int.class || type == Integer.class || - type == long.class || type == Long.class || - type == double.class || type == Double.class || - type == float.class || type == Float.class || - type == short.class || type == Short.class || - type == byte.class || type == Byte.class; - } - - private static boolean isMoreSpecific(Class[] a, Class[] b) { - for (int i = 0; i < a.length; i++) { - if (a[i] != b[i] && b[i].isAssignableFrom(a[i])) return true; - } - return false; - } - - private static Class wrap(Class type) { - if (!type.isPrimitive()) return type; - if (type == int.class) return Integer.class; - if (type == long.class) return Long.class; - if (type == boolean.class) return Boolean.class; - if (type == double.class) return Double.class; - if (type == float.class) return Float.class; - if (type == char.class) return Character.class; - if (type == byte.class) return Byte.class; - if (type == short.class) return Short.class; - return type; - } - - private String getRootCauseMessage(Throwable t) { - Throwable root = t; - while (root.getCause() != null && root.getCause() != root) { - root = root.getCause(); - } - return root.getMessage(); - } - - private boolean isTruthy(Object value) { - if (value == null) return false; - if (value instanceof Boolean) return (Boolean) value; - if (value instanceof Number) return ((Number) value).doubleValue() != 0; - if (value instanceof String) { - String s = (String) value; - if (s.isEmpty()) return false; - try { return Double.parseDouble(s) != 0; } catch (NumberFormatException ignored) {} - return !s.equalsIgnoreCase("false") && !s.equalsIgnoreCase("null"); - } - return true; - } - - private double toDouble(Object value) { - return Double.parseDouble(String.valueOf(value)); - } - - @SuppressWarnings("unchecked") - private List toList(Object obj) { - if (obj == null) return Collections.emptyList(); - if (obj instanceof List) return (List) obj; - if (obj.getClass().isArray()) { - int length = Array.getLength(obj); - List list = new ArrayList<>(length); - for (int i = 0; i < length; i++) list.add(Array.get(obj, i)); - return list; - } - throw new IllegalArgumentException("Expected array or List, but got: " + obj.getClass().getName()); - } -} 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 index 699deb6c..09f55d50 100644 --- 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 @@ -16,6 +16,7 @@ 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; @@ -31,6 +32,32 @@ 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; } diff --git a/fdc3-context/pom.xml b/fdc3-context/pom.xml index bc06ecda..0aaff42e 100644 --- a/fdc3-context/pom.xml +++ b/fdc3-context/pom.xml @@ -20,6 +20,10 @@ 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 @@ -69,6 +73,31 @@ + + + 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 @@ -144,7 +173,7 @@ npm - install @finos/fdc3-context@${fdc3.context.version} @finos/fdc3-schema@${fdc3.context.version} quicktype --save + ${fdc3.npm.context.packages} @@ -167,7 +196,7 @@ ${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 node_modules/@finos/fdc3-schema/dist/schemas/api/api.schema.json $(find node_modules/@finos/fdc3-context/dist/schemas/context -name "*.schema.json" -exec printf "%s %s " "--src" {} \;) + 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" {} \;) @@ -237,4 +266,24 @@ + + + + + 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-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java b/fdc3-example-app/src/main/java/org/finos/fdc3/example/ExampleApp.java index e4f08f62..80816b75 100644 --- 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 @@ -50,7 +50,9 @@ *

* Required system properties or environment variables: *

    - *
  • {@code FDC3_WEBSOCKET_URL} - WebSocket URL for the Desktop Agent
  • + *
  • {@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 { @@ -75,8 +77,9 @@ public class ExampleApp extends JFrame { private JButton broadcastCurrencyButton; private JButton broadcastContactButton; - // WebSocket URL (set from environment or user prompt) private String websocketUrl; + private String sessionId; + private String sharedSecret; // Stored connection info for reconnection private String lastInstanceId; @@ -99,50 +102,67 @@ public ExampleApp() { * If not set, prompt the user for the URL. */ private void initializeConnection() { - // Check environment variable first - websocketUrl = System.getenv("FDC3_WEBSOCKET_URL"); - - // Also check system property as fallback - if (websocketUrl == null || websocketUrl.isEmpty()) { - websocketUrl = System.getProperty("FDC3_WEBSOCKET_URL"); - } - - if (websocketUrl == null || websocketUrl.isEmpty()) { - // Prompt user for the URL - promptForWebSocketUrl(); + websocketUrl = envOrProperty("FDC3_WEBSOCKET_URL"); + sessionId = envOrProperty("FDC3_SESSION_ID"); + sharedSecret = envOrProperty("FDC3_CONNECTION_SECRET"); + + if (websocketUrl == null || sessionId == null || sharedSecret == null) { + promptForConnectionConfig(); } else { - // URL is set, proceed with connection - log("Using FDC3_WEBSOCKET_URL: " + websocketUrl); + log("Using WSCP connection config from environment"); connectToAgent(); } } - - /** - * Show a dialog prompting the user for the WebSocket URL. - */ - private void promptForWebSocketUrl() { - String message = "FDC3_WEBSOCKET_URL environment variable is not set.\n\n" + - "Please enter the WebSocket URL to connect to the Desktop Agent.\n" + - "You can find this URL in the Sail app directory for native apps.\n\n" + - "Example: ws://localhost:8090/remote/user-abc123/1a2b3c4d5e6f"; - - String url = JOptionPane.showInputDialog( - this, - message, - "Enter WebSocket URL", + + 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()) { - websocketUrl = url.trim(); - log("Using user-provided WebSocket URL: " + websocketUrl); - connectToAgent(); - } else { - // User cancelled or entered empty string - statusLabel.setText("Not Connected"); - statusLabel.setForeground(Color.RED); - log("No WebSocket URL provided. Cannot connect to Desktop Agent."); - log("Set FDC3_WEBSOCKET_URL environment variable or restart and enter URL."); + 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() { @@ -275,17 +295,19 @@ public void windowClosing(WindowEvent e) { } private void connectToAgent() { - if (websocketUrl == null || websocketUrl.isEmpty()) { - log("ERROR: No WebSocket URL configured"); + 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) @@ -367,8 +389,7 @@ private void onConnectionError(Throwable error) { "Enter New URL"); if (result == 0) { - // User wants to try a different URL - promptForWebSocketUrl(); + promptForConnectionConfig(); } } @@ -423,11 +444,11 @@ private void disconnect() { * Reconnect to the Desktop Agent using the stored instanceId and instanceUuid. */ private void reconnect() { - if (websocketUrl == null || websocketUrl.isEmpty()) { - log("ERROR: No WebSocket URL configured for reconnection"); + 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; @@ -445,6 +466,7 @@ private void reconnect() { GetAgentParams params = GetAgentParams.builder() .timeoutMs(30000) .webSocketUrl(websocketUrl) + .sessionId(sessionId) .instanceId(lastInstanceId) .instanceUuid(lastInstanceUuid) .build(); diff --git a/fdc3-get-agent/pom.xml b/fdc3-get-agent/pom.xml index c2b720f3..b94884ed 100644 --- a/fdc3-get-agent/pom.xml +++ b/fdc3-get-agent/pom.xml @@ -18,6 +18,8 @@ UTF-8 11 11 + 7.15.0 + 6.1.2 @@ -56,6 +58,20 @@ 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 @@ -63,11 +79,53 @@ 2.0.9 - + - org.finos.fdc3 - fdc3-testing - ${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 @@ -91,9 +149,7 @@ cucumber.junit-platform.naming-strategy=long - cucumber.plugin=pretty,html:target/cucumber-reports/cucumber.html - cucumber.glue=org.finos.fdc3.getagent,org.finos.fdc3.getagent.steps,org.finos.fdc3.testing.steps - cucumber.features=classpath:features + 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 index ce921f52..18d41398 100644 --- 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 @@ -37,59 +37,27 @@ 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.WebConnectionProtocol1HelloMeta; -import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentity; -import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentityPayload; -import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentityType; +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. - *

- * This class handles the Web Connection Protocol (WCP) handshake to establish - * a connection to an FDC3 Desktop Agent over WebSocket. - *

- * Usage example: - *

{@code
- * GetAgentParams params = GetAgentParams.builder()
- *     .webSocketUrl("ws://localhost:8080/fdc3")
- *     .instanceId("my-app-instance-1")
- *     .instanceUuid("550e8400-e29b-41d4-a716-446655440000")
- *     .channelSelector(myChannelSelector)
- *     .intentResolver(myIntentResolver)
- *     .build();
- *
- * DesktopAgent agent = GetAgent.getAgent(params).toCompletableFuture().get();
- * }
+ * Factory for obtaining a DesktopAgent connection via WebSocket using WSCP. */ public class GetAgent { - private static final String WCP5_VALIDATE_APP_IDENTITY_RESPONSE = "WCP5ValidateAppIdentityResponse"; - private static final String WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE = "WCP5ValidateAppIdentityFailedResponse"; + private static final String WSCP2_CONNECT_RESPONSE = "WSCP2ConnectResponse"; + private static final String WSCP2_CONNECT_FAILED_RESPONSE = "WSCP2ConnectFailedResponse"; private GetAgent() { - // Utility class - no instantiation } - /** - * Obtains a DesktopAgent connection using the provided parameters. - *

- * This method: - *

    - *
  1. Establishes a WebSocket connection to the Desktop Agent
  2. - *
  3. Sends a WCP4ValidateAppIdentity message
  4. - *
  5. Waits for WCP5ValidateAppIdentityResponse or WCP5ValidateAppIdentityFailedResponse
  6. - *
  7. If successful, constructs and returns a DesktopAgentProxy
  8. - *
- * - * @param params the connection parameters - * @return a CompletionStage that completes with the DesktopAgent, or fails with an exception - * @throws FDC3ConnectionException if the connection fails - */ public static CompletionStage getAgent(GetAgentParams params) { Logger.info("Initiating Desktop Agent connection to {}", params.getWebSocketUrl()); - // Create a temporary AppIdentifier for the initial connection - // This will be updated once we receive the identity validation response AppIdentifier tempAppId = new AppIdentifier("pending", null, null); WebSocketMessaging messaging = new WebSocketMessaging(params.getWebSocketUrl(), tempAppId); @@ -97,13 +65,12 @@ public static CompletionStage getAgent(GetAgentParams params) { .thenCompose(v -> performHandshake(messaging, params)) .thenApply(validationResult -> createDesktopAgent(messaging, validationResult, params)) .exceptionally(error -> { - // Clean up on failure try { messaging.disconnect(); } catch (Exception e) { Logger.error("Error during cleanup: {}", e.getMessage()); } - + if (error.getCause() instanceof FDC3ConnectionException) { throw (FDC3ConnectionException) error.getCause(); } @@ -111,37 +78,42 @@ public static CompletionStage getAgent(GetAgentParams params) { }); } - /** - * Performs the WCP4/WCP5 handshake with the Desktop Agent. - */ private static CompletionStage performHandshake( WebSocketMessaging messaging, GetAgentParams params) { - + String connectionAttemptUuid = UUID.randomUUID().toString(); - // Build WCP4ValidateAppIdentity message using schema classes - WebConnectionProtocol4ValidateAppIdentity validateMsg = new WebConnectionProtocol4ValidateAppIdentity(); - validateMsg.setType(WebConnectionProtocol4ValidateAppIdentityType.WCP4_VALIDATE_APP_IDENTITY); + WebSocketConnectionProtocol1ConnectRequest connectMsg = + new WebSocketConnectionProtocol1ConnectRequest(); + connectMsg.setType(WebSocketConnectionProtocol1ConnectRequestType.WSCP1_CONNECT_REQUEST); - WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta(); + WebSocketConnectionProtocol1ConnectRequestMeta meta = + new WebSocketConnectionProtocol1ConnectRequestMeta(); meta.setConnectionAttemptUUID(connectionAttemptUuid); meta.setTimestamp(OffsetDateTime.now()); - validateMsg.setMeta(meta); - - WebConnectionProtocol4ValidateAppIdentityPayload payload = new WebConnectionProtocol4ValidateAppIdentityPayload(); - // For native apps, use "native" as the identity and actual URLs - payload.setIdentityURL("native"); - payload.setActualURL("native"); - - // Include instanceId and instanceUuid (required for native apps) - payload.setInstanceID(params.getInstanceId()); - payload.setInstanceUUID(params.getInstanceUuid()); - validateMsg.setPayload(payload); + 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); - // Convert to Map for sending - Map validateMessage = messaging.getConverter().toMap(validateMsg); + Map connectMessage = messaging.getConverter().toMap(connectMsg); - // Set up response listener CompletableFuture responseFuture = new CompletableFuture<>(); messaging.register(new org.finos.fdc3.proxy.listeners.RegisterableListener() { @@ -156,12 +128,11 @@ public String getId() { @SuppressWarnings("unchecked") public boolean filter(Map message) { String type = (String) message.get("type"); - if (!WCP5_VALIDATE_APP_IDENTITY_RESPONSE.equals(type) && - !WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE.equals(type)) { + if (!WSCP2_CONNECT_RESPONSE.equals(type) + && !WSCP2_CONNECT_FAILED_RESPONSE.equals(type)) { return false; } - // Verify the connectionAttemptUuid matches Map msgMeta = (Map) message.get("meta"); if (msgMeta == null) { return false; @@ -178,34 +149,33 @@ public void action(Map message) { String type = (String) message.get("type"); Map responsePayload = (Map) message.get("payload"); - if (WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE.equals(type)) { - String errorMessage = responsePayload != null - ? (String) responsePayload.get("message") - : "Identity validation failed"; + if (WSCP2_CONNECT_FAILED_RESPONSE.equals(type)) { + String errorMessage = responsePayload != null + ? (String) responsePayload.get("message") + : "Connection failed"; responseFuture.completeExceptionally( - new FDC3ConnectionException("Identity validation failed: " + errorMessage)); + new FDC3ConnectionException("Connection failed: " + errorMessage)); return; } - // Parse successful response try { ValidationResult result = new ValidationResult(); result.appId = (String) responsePayload.get("appId"); result.instanceId = (String) responsePayload.get("instanceId"); result.instanceUuid = (String) responsePayload.get("instanceUuid"); - // Parse implementation metadata - Map implMeta = (Map) responsePayload.get("implementationMetadata"); + Map implMeta = + (Map) responsePayload.get("implementationMetadata"); if (implMeta != null) { result.implementationMetadata = parseImplementationMetadata(implMeta); } - Logger.info("Identity validation successful - appId: {}, instanceId: {}", + Logger.info("WSCP handshake successful - appId: {}, instanceId: {}", result.appId, result.instanceId); responseFuture.complete(result); } catch (Exception e) { responseFuture.completeExceptionally( - new FDC3ConnectionException("Failed to parse validation response", e)); + new FDC3ConnectionException("Failed to parse WSCP2 response", e)); } } @@ -221,17 +191,16 @@ public CompletionStage unsubscribe() { } }); - // Send the validation message - Logger.debug("Sending WCP4ValidateAppIdentity message"); - messaging.post(validateMessage); + Logger.debug("Sending WSCP1ConnectRequest message"); + messaging.post(connectMessage); - // Apply timeout 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 identity validation"); + 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; @@ -240,21 +209,18 @@ public CompletionStage unsubscribe() { }); } - /** - * Parses the implementation metadata from the validation response. - */ @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")); - // Parse app metadata Map appMeta = (Map) implMeta.get("appMetadata"); if (appMeta != null) { - org.finos.fdc3.api.metadata.AppMetadata appMetadata = new org.finos.fdc3.api.metadata.AppMetadata(); + 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")); @@ -265,15 +231,16 @@ private static ImplementationMetadata parseImplementationMetadata(Map optFeatures = (Map) implMeta.get("optionalFeatures"); if (optFeatures != null) { - ImplementationMetadata.OptionalFeatures features = new ImplementationMetadata.OptionalFeatures(); + 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")); + features.setUserChannelMembershipAPIs( + (Boolean) optFeatures.get("UserChannelMembershipAPIs")); } if (optFeatures.get("DesktopAgentBridging") != null) { features.setDesktopAgentBridging((Boolean) optFeatures.get("DesktopAgentBridging")); @@ -284,25 +251,18 @@ private static ImplementationMetadata parseImplementationMetadata(Map connectables = new ArrayList<>(); connectables.add(heartbeatSupport); - // Create and return the DesktopAgentProxy DesktopAgentProxy proxy = new DesktopAgentProxy( heartbeatSupport, channelSupport, @@ -328,16 +286,12 @@ private static DesktopAgent createDesktopAgent( appSupport, connectables); - // Start the heartbeat and other connectables proxy.connect(); Logger.info("DesktopAgent proxy created successfully"); return proxy; } - /** - * Holds the result of a successful identity validation. - */ private static class ValidationResult { String appId; String instanceId; 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 index 8a4cb65b..c0f060b9 100644 --- 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 @@ -24,21 +24,25 @@ /** * Parameters for obtaining a DesktopAgent connection via WebSocket. *

- * This class contains all the configuration needed to establish a connection - * to an FDC3 Desktop Agent over WebSocket using the Web Connection Protocol (WCP). + * Uses the WebSocket Connection Protocol (WSCP) for the initial handshake. *

- * The following system properties can be used to provide default values: + * The following environment variables / system properties provide defaults: *

    - *
  • {@code FDC3_WEBSOCKET_URL} - Default WebSocket URL
  • + *
  • {@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)
  • *
- * Values set via the builder will override system property defaults. */ public class GetAgentParams { - /** System property name for the WebSocket URL default. */ 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; @@ -50,6 +54,9 @@ public class GetAgentParams { 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; @@ -60,105 +67,69 @@ private GetAgentParams(Builder builder) { this.heartbeatIntervalMs = builder.heartbeatIntervalMs; } - /** - * Gets the WebSocket URL to connect to the Desktop Agent. - * - * @return the WebSocket URL - */ public String getWebSocketUrl() { return webSocketUrl; } - /** - * Gets the instance ID used to identify this application instance. - * This is sent to the Desktop Agent during the handshake. - * - * @return the instance ID - */ + public String getSessionId() { + return sessionId; + } + + public String getSharedSecret() { + return sharedSecret; + } + + public String getAppId() { + return appId; + } + public String getInstanceId() { return instanceId; } - /** - * Gets the instance UUID used as a shared secret with the Desktop Agent. - * This is used to validate the application's identity during reconnection. - * - * @return the instance UUID - */ public String getInstanceUuid() { return instanceUuid; } - /** - * Gets the channel selector implementation for user channel selection UI. - * - * @return the channel selector - */ public ChannelSelector getChannelSelector() { return channelSelector; } - /** - * Gets the intent resolver implementation for intent resolution UI. - * - * @return the intent resolver - */ public IntentResolver getIntentResolver() { return intentResolver; } - /** - * Gets the connection timeout in milliseconds. - * - * @return the timeout in milliseconds - */ public long getTimeoutMs() { return timeoutMs; } - /** - * Gets the message exchange timeout in milliseconds. - * This is used for API calls to the Desktop Agent. - * - * @return the message exchange timeout - */ public long getMessageExchangeTimeout() { return messageExchangeTimeout; } - /** - * Gets the app launch timeout in milliseconds. - * This is used when opening apps or raising intents. - * - * @return the app launch timeout - */ public long getAppLaunchTimeout() { return appLaunchTimeout; } - /** - * Gets the heartbeat interval in milliseconds. - * - * @return the heartbeat interval - */ public long getHeartbeatIntervalMs() { return heartbeatIntervalMs; } - /** - * Creates a new builder for GetAgentParams. - * - * @return a new builder instance - */ public static Builder builder() { return new Builder(); } - /** - * Builder for GetAgentParams. - */ public static class Builder { - private String webSocketUrl = System.getProperty(PROP_WEBSOCKET_URL); + 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(); @@ -168,115 +139,88 @@ public static class Builder { private long appLaunchTimeout = 30000; private long heartbeatIntervalMs = 5000; - /** - * Sets the WebSocket URL to connect to the Desktop Agent. - * - * @param webSocketUrl the WebSocket URL (required) - * @return this builder - */ + 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; } - /** - * Sets the instance ID used to identify this application instance. - * - * @param instanceId the instance ID (required) - * @return this builder - */ + 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; } - /** - * Sets the instance UUID used as a shared secret with the Desktop Agent. - * - * @param instanceUuid the instance UUID (required) - * @return this builder - */ public Builder instanceUuid(String instanceUuid) { this.instanceUuid = instanceUuid; return this; } - /** - * Sets the channel selector implementation. - * - * @param channelSelector the channel selector (default: NullChannelSelector) - * @return this builder - */ public Builder channelSelector(ChannelSelector channelSelector) { this.channelSelector = channelSelector != null ? channelSelector : new DefaultChannelSelector(); return this; } - /** - * Sets the intent resolver implementation. - * - * @param intentResolver the intent resolver (default: NullIntentResolver) - * @return this builder - */ public Builder intentResolver(IntentResolver intentResolver) { this.intentResolver = intentResolver != null ? intentResolver : new DefaultIntentResolver(); return this; } - /** - * Sets the connection timeout in milliseconds. - * - * @param timeoutMs the timeout (default: 10000) - * @return this builder - */ public Builder timeoutMs(long timeoutMs) { this.timeoutMs = timeoutMs; return this; } - /** - * Sets the message exchange timeout in milliseconds. - * - * @param messageExchangeTimeout the timeout (default: 10000) - * @return this builder - */ public Builder messageExchangeTimeout(long messageExchangeTimeout) { this.messageExchangeTimeout = messageExchangeTimeout; return this; } - /** - * Sets the app launch timeout in milliseconds. - * - * @param appLaunchTimeout the timeout (default: 30000) - * @return this builder - */ public Builder appLaunchTimeout(long appLaunchTimeout) { this.appLaunchTimeout = appLaunchTimeout; return this; } - /** - * Sets the heartbeat interval in milliseconds. - * - * @param heartbeatIntervalMs the interval (default: 5000) - * @return this builder - */ public Builder heartbeatIntervalMs(long heartbeatIntervalMs) { this.heartbeatIntervalMs = heartbeatIntervalMs; return this; } - /** - * Builds the GetAgentParams instance. - * - * @return the built GetAgentParams - * @throws IllegalArgumentException if required parameters are missing - */ 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 index b7ed7362..59b57858 100644 --- 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 @@ -180,25 +180,30 @@ public CompletionStage disconnect() { return CompletableFuture.completedFuture(null); } - // Send WCP6Goodbye message before closing + // Send WSCP3Goodbye message before closing Map goodbye = new HashMap<>(); - goodbye.put("type", "WCP6Goodbye"); + goodbye.put("type", "WSCP3Goodbye"); Map meta = new HashMap<>(); meta.put("timestamp", OffsetDateTime.now()); goodbye.put("meta", meta); - return post(goodbye).whenComplete((v, error) -> { - // Close the WebSocket regardless of whether the goodbye was sent successfully - try { - session.close(); - } catch (IOException e) { - Logger.error("Error closing WebSocket: {}", e.getMessage()); - } - - // Clear all listeners - listeners.clear(); - connected = false; - }); + 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); } /** 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 index ec37feba..06b6652c 100644 --- 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 @@ -26,20 +26,16 @@ /** * Spring configuration for Cucumber tests. - * - * Scans both local step definitions and the generic steps from fdc3-testing. + * + * Scans GetAgent-specific step definitions and generic steps from standard-cucumber-steps. */ @Configuration @ComponentScan(basePackages = { "org.finos.fdc3.getagent.steps", - "org.finos.fdc3.testing.steps" + "io.github.robmoffat.steps" }) public class TestSpringConfig { - /** - * Create PropsWorld as a scenario-scoped bean. - * This is shared across all step definition classes within a scenario. - */ @Bean @ScenarioScope(proxyMode = ScopedProxyMode.NO) public PropsWorld 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 index e69de29b..d248aa53 100644 --- 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 @@ -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 index 2c98750e..2815f8d5 100644 --- 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 @@ -18,6 +18,7 @@ import java.io.IOException; import java.time.OffsetDateTime; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import jakarta.websocket.CloseReason; @@ -30,41 +31,38 @@ 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.WebConnectionProtocol1HelloMeta; -import org.finos.fdc3.schema.WebConnectionProtocol4ValidateAppIdentity; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponse; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentityFailedResponseType; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponse; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload; -import org.finos.fdc3.schema.WebConnectionProtocol5ValidateAppIdentitySuccessResponseType; -import org.finos.fdc3.schema.WebConnectionProtocol6Goodbye; +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. - * Simulates a Desktop Agent responding to WCP messages. - * - * Uses schema types from fdc3-schema for type-safe JSON handling: - * - WebConnectionProtocol4ValidateAppIdentity - * - WebConnectionProtocol5ValidateAppIdentitySuccessResponse - * - WebConnectionProtocol5ValidateAppIdentityFailedResponse - * - WebConnectionProtocol6Goodbye + * Mock WebSocket server for testing GetAgent WSCP handshake. */ @ServerEndpoint("/fdc3") public class MockWebSocketServer { private static final SchemaConverter converter = new SchemaConverter(); - // Instance-specific state (accessed via static holder for @ServerEndpoint) private static final CopyOnWriteArrayList sessions = new CopyOnWriteArrayList<>(); private static volatile MockWebSocketServer currentInstance; private Server server; private int port; - - // Configuration for responses - private String acceptedIdentityUrl; + + private String acceptedSessionId; + private String acceptedSharedSecret; private String responseAppId; private String responseInstanceId; private String responseInstanceUuid = "response-uuid"; @@ -72,10 +70,9 @@ public class MockWebSocketServer { private boolean shouldTimeout; private String providerName = "test-provider"; private String fdc3Version = "2.0"; - - // Received messages for assertions (using typed schema classes) - private WebConnectionProtocol4ValidateAppIdentity lastWCP4; - private WebConnectionProtocol6Goodbye lastWCP6; + + private WebSocketConnectionProtocol1ConnectRequest lastWSCP1; + private WebSocketConnectionProtocol3Goodbye lastWSCP3; public void start() throws Exception { port = 8025 + (int) (Math.random() * 1000); @@ -97,21 +94,23 @@ public String getUrl() { return "ws://localhost:" + port + "/fdc3"; } - public void acceptIdentity(String identityUrl, String appId, String instanceId) { - acceptedIdentityUrl = identityUrl; + 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 rejectIdentity(String message) { - acceptedIdentityUrl = null; + public void rejectPairing(String message) { + acceptedSessionId = null; + acceptedSharedSecret = null; rejectMessage = message; shouldTimeout = false; } - public void timeoutIdentity() { + public void timeoutPairing() { shouldTimeout = true; } @@ -120,18 +119,14 @@ public void setImplementationMetadata(String provider, String version) { fdc3Version = version; } - // Property accessors for assertions - public WebConnectionProtocol4ValidateAppIdentity getLastWCP4() { - return lastWCP4; + public WebSocketConnectionProtocol1ConnectRequest getLastWSCP1() { + return lastWSCP1; } - public WebConnectionProtocol6Goodbye getLastWCP6() { - return lastWCP6; + public WebSocketConnectionProtocol3Goodbye getLastWSCP3() { + return lastWSCP3; } - // WebSocket endpoint methods - these are called on the endpoint instance - // created by the container, so we delegate to the currentInstance - @OnOpen public void onOpen(Session session) { sessions.add(session); @@ -145,55 +140,66 @@ public void onClose(Session session, CloseReason reason) { @OnMessage public void onMessage(String message, Session session) { try { - // First, peek at the type to determine which class to use String type = extractType(message); - - // Delegate to current instance for state management MockWebSocketServer instance = currentInstance; if (instance == null) { return; } - if ("WCP4ValidateAppIdentity".equals(type)) { - WebConnectionProtocol4ValidateAppIdentity wcp4 = - converter.fromJson(message, WebConnectionProtocol4ValidateAppIdentity.class); - instance.lastWCP4 = wcp4; - instance.handleValidateAppIdentity(wcp4, session); - } else if ("WCP6Goodbye".equals(type)) { - WebConnectionProtocol6Goodbye wcp6 = - converter.fromJson(message, WebConnectionProtocol6Goodbye.class); - instance.lastWCP6 = wcp6; + 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(); } } - /** - * Extract the "type" field from a JSON message without full parsing. - */ private String extractType(String json) throws IOException { return converter.getObjectMapper().readTree(json).path("type").asText(null); } - private void handleValidateAppIdentity(WebConnectionProtocol4ValidateAppIdentity request, Session session) { + private void handleConnectRequest( + WebSocketConnectionProtocol1ConnectRequest request, Session session) { if (shouldTimeout) { - // Don't respond - simulate timeout return; } String connectionAttemptUuid = request.getMeta().getConnectionAttemptUUID(); - String identityUrl = request.getPayload().getIdentityURL(); + 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 (acceptedIdentityUrl != null && acceptedIdentityUrl.equals(identityUrl)) { + } 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, "Unknown identity URL: " + identityUrl); + responseJson = buildFailedResponse(connectionAttemptUuid, "Invalid pairing credentials"); } session.getBasicRemote().sendText(responseJson); @@ -202,70 +208,86 @@ private void handleValidateAppIdentity(WebConnectionProtocol4ValidateAppIdentity } } - private String buildSuccessResponse(String connectionAttemptUuid) throws IOException { - WebConnectionProtocol5ValidateAppIdentitySuccessResponse response = - new WebConnectionProtocol5ValidateAppIdentitySuccessResponse(); + private void handleGetInfoRequest(GetInfoRequest request, Session session) throws IOException { + GetInfoResponse response = new GetInfoResponse(); + response.setType(GetInfoResponseType.GET_INFO_RESPONSE); - // Build meta - WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta(); - meta.setConnectionAttemptUUID(connectionAttemptUuid); + 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); - // Set type - response.setType(WebConnectionProtocol5ValidateAppIdentitySuccessResponseType.WCP5_VALIDATE_APP_IDENTITY_RESPONSE); + GetInfoResponsePayload payload = new GetInfoResponsePayload(); + payload.setImplementationMetadata(buildImplementationMetadata()); + response.setPayload(payload); - // Build payload - WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload payload = - new WebConnectionProtocol5ValidateAppIdentitySuccessResponsePayload(); - payload.setAppID(responseAppId); - payload.setInstanceID(responseInstanceId); - payload.setInstanceUUID(responseInstanceUuid); + session.getBasicRemote().sendText(converter.toJson(response)); + } - // Build implementation metadata + private ImplementationMetadata buildImplementationMetadata() { ImplementationMetadata implMeta = new ImplementationMetadata(); implMeta.setFdc3Version(fdc3Version); implMeta.setProvider(providerName); implMeta.setProviderVersion("1.0.0"); - // Build app metadata AppMetadata appMeta = new AppMetadata(); appMeta.setAppId(responseAppId); appMeta.setInstanceId(responseInstanceId); implMeta.setAppMetadata(appMeta); - // Build optional features ImplementationMetadata.OptionalFeatures optFeatures = new ImplementationMetadata.OptionalFeatures(); optFeatures.setOriginatingAppMetadata(true); optFeatures.setUserChannelMembershipAPIs(true); optFeatures.setDesktopAgentBridging(false); implMeta.setOptionalFeatures(optFeatures); - payload.setImplementationMetadata(implMeta); + 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 { - WebConnectionProtocol5ValidateAppIdentityFailedResponse response = - new WebConnectionProtocol5ValidateAppIdentityFailedResponse(); + WebSocketConnectionProtocol2ConnectFailedResponse response = + new WebSocketConnectionProtocol2ConnectFailedResponse(); - // Build meta - WebConnectionProtocol1HelloMeta meta = new WebConnectionProtocol1HelloMeta(); + WebSocketConnectionProtocol1ConnectRequestMeta meta = + new WebSocketConnectionProtocol1ConnectRequestMeta(); meta.setConnectionAttemptUUID(connectionAttemptUuid); meta.setTimestamp(OffsetDateTime.now()); response.setMeta(meta); - // Set type - response.setType(WebConnectionProtocol5ValidateAppIdentityFailedResponseType.WCP5_VALIDATE_APP_IDENTITY_FAILED_RESPONSE); + response.setType( + WebSocketConnectionProtocol2ConnectFailedResponseType.WSCP2_CONNECT_FAILED_RESPONSE); - // Build payload - WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload payload = - new WebConnectionProtocol5ValidateAppIdentityFailedResponsePayload(); + 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 index dc154fdb..ff3f6a61 100644 --- a/fdc3-get-agent/src/test/resources/features/get-agent.feature +++ b/fdc3-get-agent/src/test/resources/features/get-agent.feature @@ -3,50 +3,57 @@ Feature: GetAgent WebSocket Connection Background: Test Setup Given a mock WebSocket server in "server" - Scenario: Successful connection with valid identity - Given "server" will accept identity for "https://myapp.example.com/" as appId "test-app" instanceId "test-instance" - Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" - When I call "GetAgent" with "getAgent" with parameter "{params}" - Then the promise "result" should resolve + 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 refer to the server "server" last WCP4 as "wcp4" - Then "{wcp4}" is an object with the following contents - | payload.identityURL | payload.instanceID | payload.instanceUUID | - | https://myapp.example.com/ | test-instance | test-uuid-123 | + 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 identity - Given "server" will reject identity with message "Access denied" - Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://unknown.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" - When I call "GetAgent" with "getAgent" with parameter "{params}" - Then the promise "result" should resolve - And "{result}" is an error with message "Access denied" + 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 identity validation - Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" timeout 2000 - When I call "GetAgent" with "getAgent" with parameter "{params}" - Then the promise "result" should resolve + 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 identity for "https://myapp.example.com/" as appId "test-app" instanceId "test-instance" + 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}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" - When I call "GetAgent" with "getAgent" with parameter "{params}" - Then the promise "result" should resolve + 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" - Then the promise "result" should resolve And "{result}" is an object with the following contents | provider | fdc3Version | | test-provider | 2.0 | - Scenario: Disconnect sends WCP6Goodbye - Given "server" will accept identity for "https://myapp.example.com/" as appId "test-app" instanceId "test-instance" - Given "params" is GetAgentParams with webSocketUrl "{server.url}" identityUrl "https://myapp.example.com/" instanceId "test-instance" instanceUuid "test-uuid-123" - When I call "GetAgent" with "getAgent" with parameter "{params}" - Then the promise "result" should resolve + 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" - Then the promise "result" should resolve - When I refer to the server "server" last WCP6 as "wcp6" - Then "{wcp6}" is not null + 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 index 282b29e1..50da95ce 100644 --- a/fdc3-schema/pom.xml +++ b/fdc3-schema/pom.xml @@ -295,17 +295,17 @@ import org.finos.fdc3.api.types.AppIdentifier; - + download - - true - - + local + + true + ${project.basedir}/src/main/schemas-temp/3.0.0 ${project.basedir}/../fdc3-context/src/schemas-temp/3.0.0/context 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/pom.xml b/pom.xml index 55b32632..336242e9 100644 --- a/pom.xml +++ b/pom.xml @@ -37,5 +37,19 @@ 11 + + + + local + + true + + + + + + download + +