Application-to-application communication
Application-to-application communication
Zowe™ application plug-ins can opt-in to various application framework abilities, such as the ability to have a Logger, the ability to use a URI builder utility, and more.
The ability for one appliccation plug-in to communicate with another is an ability that is unique to Zowe environments with multiple application plug-ins. The application framework provides constructs that facilitate this ability.
The constructs are: the Dispatcher, Actions, Recognizers, Registry, and the features that utilize them such as the framework's Context menu.
- Why use application-to-application communication?
- Actions
- Recognizers
- Dispatcher
- Registry
- Pulling it all together in an example
Why use application-to-application communication?
When working with computers, people often use multiple applications to accomplish a task. For example, a person might check their email before opening a bank statement in a browser. In many environments, the relationship between one application and another is not well defined. For example, you may open one program to learn of a situation, which is then resolved by opening a different program and typing in content. The application framework attempts to solve this problem by creating structured messages that can be sent from one application plug-in to another.
An application plug-in has a context of the information that it contains. This context can be used to invoke an action on another application plug-in that is better suited to handle some of the information discovered in the first application plug-in. Well-structured messages facilitate the process of determining which application plug-in is best suited to handle a given situation, while also explaining, in detail, what that application plug-in should do.
This way, rather than finding out that an attachment with the extension ".dat" was not meant for a text editor, but rather for an email client, one application plug-in may be able to invoke an action on an application plug-in that is capable of opening of an email.
Actions
To manage communication from one application plug-in to another, a specific structure is needed. In the application framework, the unit of application-to-application communication is an Action. The typescript definition of an Action is as follows:
export class Action implements ZLUX.Action {
id: string; // id of action itself.
i18nNameKey: string; // future proofing for I18N
defaultName: string; // default name for display purposes, w/o I18N
description: string;
targetMode: ActionTargetMode;
type: ActionType; // "launch", "message"
targetPluginID: string;
primaryArgument: any;
constructor(id: string,
defaultName: string,
targetMode: ActionTargetMode,
type: ActionType,
targetPluginID: string,
primaryArgument:any) {
this.id = id;
this.defaultName = defaultName;
// proper name for ID/type
this.targetPluginID = targetPluginID;
this.targetMode = targetMode;
this.type = type;
this.primaryArgument = primaryArgument;
}
getDefaultName():string {
return this.defaultName;
}
}
An Action has a specific structure of data that is passed, to be filled in with the context at runtime, and a specific target to receive the data.
The Action is dispatched to the target in one of several modes, for example: to target a specific instance of an application plug-in, an instance, or to create a new instance.
The Action can be less detailed than a message. It can be a request to minimize, maximize, close, launch, and more. Finally, all of this information is related to a unique ID and localization string such that it can be managed by the framework.
Action target modes
When you request an Action on an application plug-in, the behavior is dependent on the instance of the application plug-in you are targeting.
You can instruct the framework to target the application plug-in with a target mode from the ActionTargetMode
enum
:
export enum ActionTargetMode {
PluginCreate, // require pluginType
PluginFindUniqueOrCreate, // required AppInstance/ID
PluginFindAnyOrCreate, // plugin type
//TODO PluginFindAnyOrFail
System, // something that is always present
}
Action types
The application framework performs different operations on application plug-ins depending on the type of an Action. The behavior can be quite different, from simple messaging to requesting that an application plug-in be minimized. The types are defined by an enum
:
export enum ActionType { // not all actions are meaningful for all target modes
Launch, // essentially do nothing after target mode
Focus, // bring to fore, but nothing else
Route, // sub-navigate or "route" in target
Message, // "onMessage" style event to plugin
Method, // Method call on instance, more strongly typed
Minimize,
Maximize,
Close, // may need to call a "close handler"
}
Loading actions
Actions can be created dynamically at runtime, or saved and loaded by the system at login.
App2App via URL
Another way the Zowe Application Framework invokes Actions is via URL Query Parameters, with parameters formatted in JSON. This feature enables users to bookmark a set of application-to-application communication actions (in the form of a URL) that will be executed when opening the webpage. Developers creating separate web apps can build a link that will open the Zowe Desktop and do specific actions in Apps, for example, opening a file in the Editor.
The App2App via URL feature allows you to:
-
Specify one or more actions that will be executed upon login, allowing you to bookmark a series of actions that you can share with someone else.
-
Specify actions that are declared by plugins (when formatter is equal to a known action ID) or actions that you have custom-made (when formatter = 'data').
-
Customize the action type, mode, and target plugin (when the formatter is equal to an existing action ID).
Samples
https://localhost:7556/ZLUX/plugins/org.zowe.zlux.bootstrap/web/?app2app=org.zowe.zlux.ng2desktop.webbrowser:launch:create:data:{"url":"https://github.com/zowe/zlux-app-manager/pull/234","enableProxy":true}&app2app=org.zowe.zlux.ng2desktop.webbrowser:message:create:data:{"url":"https://github.com/zowe/zlux-app-manager/pull/234","enableProxy":true}&app2app=org.zowe.zlux.ng2desktop.webbrowser:message:create:org.zowe.zlux.test.action:{"data": {"url":"https://github.com/zowe/zlux-app-manager/pull/234","enableProxy":true}}
https://localhost:7556/ZLUX/plugins/org.zowe.zlux.bootstrap/web/?pluginId=org.zowe.terminal.tn3270&showLogin=true
https://localhost:7556/ZLUX/plugins/org.zowe.zlux.bootstrap/web/?pluginId=org.zowe.editor:data:{"type":"openFile","name":"/u/yourhome/notes.txt"}
Query parameter format:
?app2app={pluginId}:{actionType}:{actionMode}:{formatter}:{contextData}&app2app={pluginId}:{actionType}:{actionMode}:{formatter}:{contextData}
pluginId
- application identifier, e.g.'org.zowe.zlux.ng2desktop.webbrowser'
actionType
-'launch' | 'message'
actionMode
-'create' | 'system'
formatter
-'data'
| actionIdcontextData
- context data in form of JSONwindowManager
-'MVD' | undefined
: (Optional) While in standalone mode, controls whether to use the Zowe (MVD) window manager or the deprecated simple window manager. Default is MVD.showLogin
-true | false
: (Optional) While in standalone mode, controls whether to show Zowe's login page if credentials are not retrieved from a previous Desktop session, or if to disable it and load the application anyway (ideal solution for apps with their own login experiences). Default is true.
Note that some of these parameters are shared with single app mode, therefore, you may need to adjust pluginId and app2app parameters as follows
(desktop mode)
app2app=xxx.xxx.xxx:type:mode:formatter:{contextdata ...}
(single app mode)
pluginId=xxx.xxx.xxx:formatter:{"xxx":"xxx" ...}
Dynamically
You can create Actions by calling the following Dispatcher method: makeAction(id: string, defaultName: string, targetMode: ActionTargetMode, type: ActionType, targetPluginID: string, primaryArgument: any):Action
Saved on system
Actions can be stored in JSON files that are loaded at login. The JSON structure is as follows:
{
"actions": [
{
"id":"org.zowe.explorer.openmember",
"defaultName":"Edit PDS in MVS Explorer",
"type":"Launch",
"targetMode":"PluginCreate",
"targetId":"org.zowe.explorer",
"arg": {
"type": "edit_pds",
"pds": {
"op": "deref",
"source": "event",
"path": [
"full_path"
]
}
}
}
]
}
Recognizers
Actions are meant to be invoked when certain conditions are met. For example, you do not need to open a messaging window if you have no one to message. Recognizers are objects within the application framework that use the context that the application plug-in provides to determine if there is a condition for which it makes sense to execute an Action. Each recognizer has statements about what condition to recognize, and when that statement is met, which Action can be executed at that time. The invocation of the Action is not handled by the Recognizer; it simply detects that an Action can be taken.
Recognition clauses
Recognizers associate a clause of recognition with an action, as you can see from the following class:
export class RecognitionRule {
predicate:RecognitionClause;
actionID:string;
constructor(predicate:RecognitionClause, actionID:string){
this.predicate = predicate;
this.actionID = actionID;
}
}
A clause, in turn, is associated with an operation, and the subclauses upon which the operation acts. The following operations are supported:
export enum RecognitionOp {
AND,
OR,
NOT,
PROPERTY_EQ,
SOURCE_PLUGIN_TYPE, // syntactic sugar
MIME_TYPE, // ditto
}
Loading Recognizers at runtime
You can add a Recognizer to the application plug-in environment in one of two ways: by loading from Recognizers saved on the system, or by adding them dynamically.
Dynamically
You can call the Dispatcher method, addRecognizer(predicate:RecognitionClause, actionID:string):void
Saved on system
Recognizers can be stored in JSON files that are loaded at login. The JSON structure is as follows:
{
"recognizers": [
{
"id":"<actionID>",
"clause": {
<clause>
}
}
]
}
clause can take on one of two shapes:
"prop": ["<keyString>", <"valueString">]
Or,
"op": "<op enum as string>",
"args": [
{<clause>}
]
Where this one can again, have subclauses.
Recognizer example
Recognizers can be as simple or complex as you write them to be, but here is an example to illustrate the mechanism:
{
"recognizers":[
{
"id":"org.zowe.explorer.openmember",
"clause": {
"op": "AND",
"args": [
{"prop":["sourcePluginID","org.zowe.terminal.tn3270"]},{"prop":["screenID","ISRUDSM"]}
]
}
}
]
}
In this case, the Recognizer detects whether it is possible to run the org.zowe.explorer.openmember
Action when the TN3270 Terminal application plug-in is on the screen ISRUDSM (an ISPF panel for browsing PDS members).
Dispatcher
The dispatcher is a core component of the application framework that is accessible through the Global ZLUX
Object at runtime. The Dispatcher interprets Recognizers and Actions that are added to it at runtime. You can register Actions and Recognizers on it, and later, invoke an Action through it. The dispatcher handles how the Action's effects should be carried out, acting in combination with the Window Manager and application plug-ins to provide a channel of communication.
Registry
The Registry is a core component of the application framework, which is accessible through the Global ZLUX
Object at runtime. It contains information about which application plug-ins are present in the environment, and the abilities of each application plug-in. This is important to application-to-application communication, because a target might not be a specific application plug-in, but rather an application plug-in of a specific category, or with a specific featureset, capable of responding to the type of Action requested.
Pulling it all together in an example
The standard way to make use of application-to-application communication is by having Actions and Recognizers that are saved on the system. Actions and Recognizers are loaded at login, and then later, through a form of automation or by a user action, Recognizers can be polled to determine if there is an Action that can be executed. All of this is handled by the Dispatcher, but the description of the behavior lies in the Action and Recognizer that are used. In the Action and Recognizer descriptions above, there are two JSON definitions: One is a Recognizer that recognizes when the Terminal application plug-in is in a certain state, and another is an Action that instructs the MVS Explorer to load a PDS member for editing. When you put the two together, a practical application is that you can launch the MVS Explorer to edit a PDS member that you have selected within the Terminal application plug-in.