diff --git a/README.md b/README.md index 8ba814f..ff83aac 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,33 @@ You can build the JAR files and the launch4j executable file using the `mvn pack - JavaSwingExample - Example Java Swing application - JavaHeadlessExample - Example of a window-less Java application - AuthenticationExample - Example component that performs authentication from within Java +- MultiWindow - JavaSwingExample - Same as JavaSwingExample but running in multi-window (several windows running under the same + process) +- MultiWindow - AuthenticationExample - Same as AuthenticationExample but running in multi-window (several windows running under the same + process) +- InteropServiceExample - Example of JavaFX FDC3 Desktop Agent client ## Configuring the Java examples Copy the _java-example.json_ included in the project to _src/components/java-example/java-example.json_. Update the application manifest to include: ``` JSON "finsemble": { - "applicationRoot": "http://localhost:3375", - "moduleRoot": "http://localhost:3375/finsemble", - "servicesRoot": "http://localhost:3375/finsemble/services", - "notificationURL": "http://localhost:3375/components/notification/notification.html", - "javaExampleJarRoot": "", + ..., + "custom": { + "javaExampleJarRoot": "" + }, "importConfig": [ - "$applicationRoot/configs/application/config.json", - "$applicationRoot/components/java-example/java-example.json" - ], - "IAC": { - "serverAddress" : "ws://127.0.0.1:3376" - } + "../../configs/application/config.json", + "../../configs/application/java-example.json" + ] } ``` -**NOTE:** The _java-example.json_ file includes two copies of the JavaFX example: Java Example (local) and Java Example (asset). The "local" component uses `javaExampleRoot` to specify the path to the JAR file on the local system. The "asset" component uses the `appAsset` (described below) to download and run the application. +**NOTE:** +- The _java-example.json_ file includes two copies of the JavaFX example: + - Java Example (local) + - Java Example (asset). + +The "local" component uses `javaExampleRoot` to specify the path to the JAR file on the local system. The "asset" component uses the `appAsset` to download and run the application - Please refer to our tutorial +on [integrating native applications](https://documentation.finsemble.com/tutorial-integratingNativeApplications.html) for more details. + + diff --git a/appd.json b/appd.json new file mode 100644 index 0000000..81caa54 --- /dev/null +++ b/appd.json @@ -0,0 +1,88 @@ +{ + "appd": { + "Java FDC3 Desktop Client": { + "appId": "Java FDC3 Desktop Client", + "name": "Java FDC3 Desktop Client", + "description": "Java FDC3 Desktop Agent Client example app, by Cosaic.", + "manifest": { + "window": { + "id": "InteropServiceExampleApplication", + "windowType": "FinsembleNativeWindow", + "path": "$javaExampleJarRoot/InteropServiceExampleApplication.jar", + "arguments": "", + "defaultHeight": 600, + "autoShow": true, + "alwaysOnTop": false, + "resizable": true, + "showTaskbarIcon": false, + "contextMenu": true, + "addToWorkspace": true, + "options": { + "customData": { + "authenticationToken": "fooBar" + } + } + }, + "component": { + "mode": "native", + "category": "system" + }, + "foreign": { + "services": { + "workspaceService": { + "allowAutoArrange": true + } + }, + "components": { + "App Launcher": { + "launchableByUser": true + }, + "Window Manager": { + "persistWindowState": false, + "FSBLHeader": true + }, + "Toolbar": { + "iconURL": "$applicationRoot/assets/img/notepad.png" + } + } + }, + "interop": { + "rewire": [ + { + "contextType": "layout", + "fwd": "#Workspace" + }, + { + "contextType": "fdc3.instrument", + "cc": "#Workspace" + } + ] + } + }, + "version": "1.0.0", + "tooltip": "ChartIQ Technical Chart", + "images": [ + { + "url": "https://i.imgur.com/10C3LdH.png", + "tooltip": "ChartIQ FDC3 compatible chart" + } + ], + "tags": ["fdc3"], + "contactEmail": "info@cosaic.io", + "supportEmail": "support@finsemble.com", + "publisher": "Cosaic", + "icons": [ + { + "url": "https://i.imgur.com/mpBdiHd.png" + } + ], + "intents": [ + { + "name": "ViewChart", + "displayName": "View Chart", + "contexts": ["fdc3.instrument"] + } + ] + } + } +} \ No newline at end of file diff --git a/java-example.json b/java-example.json index b2d89fa..d40c638 100644 --- a/java-example.json +++ b/java-example.json @@ -3,9 +3,8 @@ "Java Example (local)": { "window": { "id": "JavaExample", - "windowType": "native", + "windowType": "FinsembleNativeWindow", "path": "$javaExampleJarRoot/FinsembleJavaFXExample.jar", - "url": "", "arguments": "", "defaultHeight": 600, "autoShow": true, @@ -21,7 +20,7 @@ "foreign": { "services": { "workspaceService": { - "isArrangable": true + "allowAutoArrange": true } }, "components": { @@ -41,9 +40,8 @@ "Java Example (asset)": { "window": { "id": "JavaExample", - "windowType": "native", + "windowType": "FinsembleNativeWindow", "alias": "finsembleJavaExample", - "url": "", "arguments": "", "defaultHeight": 600, "autoShow": true, @@ -59,7 +57,7 @@ "foreign": { "services": { "workspaceService": { - "isArrangable": true + "allowAutoArrange": true } }, "components": { @@ -79,9 +77,8 @@ "Java Swing Example": { "window": { "id": "JavaExample", - "windowType": "native", + "windowType": "FinsembleNativeWindow", "path": "$javaExampleJarRoot/JavaSwingExample.jar", - "url": "", "arguments": "", "defaultHeight": 600, "autoShow": true, @@ -97,7 +94,7 @@ "foreign": { "services": { "workspaceService": { - "isArrangable": true + "allowAutoArrange": true } }, "components": { @@ -116,7 +113,7 @@ }, "Java Authentication Example": { "window": { - "windowType": "native", + "windowType": "FinsembleNativeWindow", "path": "$javaExampleJarRoot/FinsembleJavaAuthenticationExample.jar", "top": "center", "left": "center", @@ -138,7 +135,7 @@ "foreign": { "services": { "workspaceService": { - "isArrangable": false + "allowAutoArrange": false } }, "components": { @@ -155,9 +152,8 @@ "Java Headless Example": { "window": { "id": "JavaExample", - "windowType": "native", + "windowType": "FinsembleNativeWindow", "path": "$javaExampleJarRoot/FinsembleJavaHeadlessExample.jar", - "url": "", "arguments": "", "defaultHeight": 600, "autoShow": true, @@ -173,7 +169,7 @@ "foreign": { "services": { "workspaceService": { - "isArrangable": true + "allowAutoArrange": true } }, "components": { @@ -189,6 +185,80 @@ } } } + }, + "MultiWindow - JavaSwingExample": { + "window": { + "id": "MultiWindowJavaSwingExample", + "windowType": "FinsembleNativeWindow", + "path": "$javaExampleJarRoot/MultiWindowJavaSwingExample.jar", + "arguments": "example=JavaSwingExample", + "defaultHeight": 600, + "autoShow": true, + "alwaysOnTop": false, + "resizable": true, + "showTaskbarIcon": false, + "contextMenu": true, + "addToWorkspace": true + }, + "component": { + "spawnOnStartup": false + }, + "foreign": { + "services": { + "workspaceService": { + "allowAutoArrange": true + } + }, + "components": { + "App Launcher": { + "launchableByUser": true + }, + "Window Manager": { + "persistWindowState": false, + "FSBLHeader": true + }, + "Toolbar": { + "iconURL": "$applicationRoot/assets/img/notepad.png" + } + } + } + }, + "MultiWindow - AuthenticationExample": { + "window": { + "id": "MultiWindowJavaSwingExample", + "windowType": "FinsembleNativeWindow", + "path": "$javaExampleJarRoot/MultiWindowJavaSwingExample.jar", + "arguments": "example=AuthenticationExample", + "defaultHeight": 600, + "autoShow": true, + "alwaysOnTop": false, + "resizable": true, + "showTaskbarIcon": false, + "contextMenu": true, + "addToWorkspace": true + }, + "component": { + "spawnOnStartup": false + }, + "foreign": { + "services": { + "workspaceService": { + "allowAutoArrange": true + } + }, + "components": { + "App Launcher": { + "launchableByUser": true + }, + "Window Manager": { + "persistWindowState": false, + "FSBLHeader": true + }, + "Toolbar": { + "iconURL": "$applicationRoot/assets/img/notepad.png" + } + } + } } } } \ No newline at end of file diff --git a/logging.properties b/logging.properties index 379dfab..04b4e74 100644 --- a/logging.properties +++ b/logging.properties @@ -2,15 +2,15 @@ handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=INFO java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter - + java.util.logging.FileHandler.level=INFO java.util.logging.FileHandler.pattern=%t/FinsembleJavaTest.log java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter - + # Write 10MB before rotating this file java.util.logging.FileHandler.limit=10000000 - + # Number of rotating files to be used java.util.logging.FileHandler.count=4 - + .level=ALL \ No newline at end of file diff --git a/pom.xml b/pom.xml index d691fee..b87337f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,16 @@ ChartIQ finesmble-java-example - 4.0.1-SNAPSHOT + 6.0.0-SNAPSHOT + + + + poolborges-github-thirdparty + poolborges-github + https://github.com/poolborges/maven/raw/master/thirdparty/ + + + org.json @@ -17,8 +26,15 @@ com.chartiq.finsemble finsemble - 4.0.1 + 6.0.0 + + it.sauronsoftware + junique + 1.0.4 + jar + + @@ -53,6 +69,25 @@ FinsembleJavaFXExample + + MultiWindowJavaSwingExample + package + + shade + + + false + + + + com.chartiq.finsemble.example.MultiWindowJavaSwingExample + + + + MultiWindowJavaSwingExample + + FinsembleJavaHeadlessExample package @@ -110,6 +145,25 @@ JavaSwingExample + + InteropServiceExampleApplication + package + + shade + + + false + + + + com.chartiq.finsemble.example.fdc3.InteropServiceExampleApplication + + + + InteropServiceExampleApplication + + diff --git a/src/main/java/com/chartiq/finsemble/example/JavaExample.java b/src/main/java/com/chartiq/finsemble/example/JavaExample.java index 5c54f05..0578b90 100644 --- a/src/main/java/com/chartiq/finsemble/example/JavaExample.java +++ b/src/main/java/com/chartiq/finsemble/example/JavaExample.java @@ -143,6 +143,11 @@ public void disconnected(ConnectionEventGenerator from) { public void error(ConnectionEventGenerator from, Exception e) { LOGGER.log(Level.SEVERE, "Error from Finsemble", e); } + + @Override + public void onWindowStateReady(ConnectionEventGenerator from) { + // NoOp + } }); appendMessage("Window registered with Finsemble"); @@ -163,11 +168,11 @@ public void error(ConnectionEventGenerator from, Exception e) { setDropable(); //GetComponentState - final JSONArray getComponentStateFields = new JSONArray(){{ + final JSONArray getComponentStateFields = new JSONArray() {{ put("Finsemble_Linker"); put("symbol"); }}; - final JSONObject getComponentStateParam = new JSONObject(){{ + final JSONObject getComponentStateParam = new JSONObject() {{ put("fields", getComponentStateFields); }}; fsbl.getClients().getWindowClient().getComponentState(getComponentStateParam, this::handleGetComponentStateCb); @@ -184,7 +189,7 @@ public void error(ConnectionEventGenerator from, Exception e) { private void handleDockingGroupUpdate(JSONObject err, JSONObject res) { if (err != null) { - fsbl.getClients().getLogger().error(err.toString()); + fsbl.getClients().getLoggerClient().system().error(err.toString()); } else { final JSONObject groupData = res.getJSONObject("data").getJSONObject("groupData"); final String currentWindowName = fsbl.getClients().getWindowClient().getWindowIdentifier().getString("windowName"); @@ -283,7 +288,8 @@ private void handleSymbol(JSONObject err, JSONObject res) { put("field", "symbol"); put("value", symbol); }}; - fsbl.getClients().getWindowClient().setComponentState(param, (e, r) -> { }); + fsbl.getClients().getWindowClient().setComponentState(param, (e, r) -> { + }); Platform.runLater(() -> symbolLabel.setText(symbol)); } } @@ -393,9 +399,9 @@ private void setFormEnable(boolean enabled) { } private void handleGetComponentStateCb(JSONObject err, JSONObject res) { - if(err!=null){ - fsbl.getClients().getLogger().error(err.toString()); - }else{ + if (err != null) { + fsbl.getClients().getLoggerClient().system().error(err.toString()); + } else { //Set subscribe linker channel if (res.has("Finsemble_Linker")) { final JSONArray channelToLink = res.getJSONArray("Finsemble_Linker"); @@ -406,7 +412,7 @@ private void handleGetComponentStateCb(JSONObject err, JSONObject res) { } //Set symbol value - if(res.has("symbol")) { + if (res.has("symbol")) { final String symbol = res.getString("symbol"); if (!symbol.equals("")) { Platform.runLater(() -> symbolLabel.setText(symbol)); @@ -565,11 +571,11 @@ public void toggleDock(ActionEvent actionEvent) { } else { fsbl.getClients().getRouterClient().query("DockingService.leaveGroup", new JSONObject() {{ put("name", currentWindowName); - }}, new JSONObject(), this::handeLeaveGroupcb); + }}, new JSONObject(), this::handleLeaveGroupCallback); checkbox.setDisable(true); } } - private void handeLeaveGroupcb(JSONObject err, JSONObject res) { + private void handleLeaveGroupCallback(JSONObject err, JSONObject res) { } } diff --git a/src/main/java/com/chartiq/finsemble/example/JavaHeadlessExample.java b/src/main/java/com/chartiq/finsemble/example/JavaHeadlessExample.java index 18dcfbd..bab1fc1 100644 --- a/src/main/java/com/chartiq/finsemble/example/JavaHeadlessExample.java +++ b/src/main/java/com/chartiq/finsemble/example/JavaHeadlessExample.java @@ -45,7 +45,7 @@ public static void main(String[] args) { */ @Override public void run() { - fsbl.getClients().getLogger().log("Headless example elapsed event was raised"); + fsbl.getClients().getLoggerClient().system().log("Headless example elapsed event was raised"); } }, 0, 1000); @@ -61,6 +61,11 @@ public void disconnected(ConnectionEventGenerator from) { public void error(ConnectionEventGenerator from, Exception e) { LOGGER.log(Level.SEVERE, "Error from Finsemble", e); } + + @Override + public void onWindowStateReady(ConnectionEventGenerator from) { + // NoOp + } }); LOGGER.info("Window registered with Finsemble"); diff --git a/src/main/java/com/chartiq/finsemble/example/JavaSwingExample.java b/src/main/java/com/chartiq/finsemble/example/JavaSwingExample.java index bb2982c..3bbf27e 100644 --- a/src/main/java/com/chartiq/finsemble/example/JavaSwingExample.java +++ b/src/main/java/com/chartiq/finsemble/example/JavaSwingExample.java @@ -53,7 +53,7 @@ public class JavaSwingExample extends JFrame implements WindowListener { * * @param args The arguments passed to the Java application from the command line */ - private JavaSwingExample(List args) { + JavaSwingExample(List args) { LOGGER.addHandler(new MessageHandler(messages)); LOGGER.info(String.format( @@ -247,6 +247,11 @@ public void disconnected(ConnectionEventGenerator from) { public void error(ConnectionEventGenerator from, Exception e) { LOGGER.log(Level.SEVERE, "Error from Finsemble", e); } + + @Override + public void onWindowStateReady(ConnectionEventGenerator from) { + // NoOp + } }); appendMessage("Window registered with Finsemble"); diff --git a/src/main/java/com/chartiq/finsemble/example/MultiWindowJavaSwingExample.java b/src/main/java/com/chartiq/finsemble/example/MultiWindowJavaSwingExample.java new file mode 100644 index 0000000..27007d8 --- /dev/null +++ b/src/main/java/com/chartiq/finsemble/example/MultiWindowJavaSwingExample.java @@ -0,0 +1,54 @@ +package com.chartiq.finsemble.example; + +import it.sauronsoftware.junique.AlreadyLockedException; +import it.sauronsoftware.junique.JUnique; + +import java.util.Arrays; + + +public class MultiWindowJavaSwingExample { + + // JUnique has a bug in which if you pass a non-unicode char, the recreation of the string from + // a inputstream gets messed up - therefore we have chosen to use & has the delimiter + // similar to what gets used in a query params URL + private static final String DELIMITER = "!&!"; + + public static void main(String[] args) { + + String appId = MultiWindowJavaSwingExample.class.getSimpleName(); + boolean alreadyRunning; + try { + JUnique.acquireLock(appId, message -> { + runExampleApplication(message.split(DELIMITER)); + return null; + }); + alreadyRunning = false; + } catch (AlreadyLockedException e) { + alreadyRunning = true; + } + if (!alreadyRunning) { + runExampleApplication(args); + } else { + JUnique.sendMessage(appId, String.join(DELIMITER, args)); + } + } + + private static void runExampleApplication(String[] args) { + + String window = Arrays.stream(args).filter(arg -> arg.startsWith("example=")).findFirst().get(); + String app = window.split("=")[1]; + switch (app) { + case "JavaSwingExample": + JavaSwingExample.main(args); + break; + + case "AuthenticationExample": + try { + AuthenticationExample.main(args); + } catch (Exception e) { + e.printStackTrace(); + } + break; + } + } +} diff --git a/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExample.java b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExample.java new file mode 100644 index 0000000..7290c7d --- /dev/null +++ b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExample.java @@ -0,0 +1,533 @@ +package com.chartiq.finsemble.example.fdc3; + +import com.chartiq.finsemble.Finsemble; +import com.chartiq.finsemble.fdc3.*; +import com.chartiq.finsemble.fdc3.channel.Channel; +import com.chartiq.finsemble.fdc3.channel.ChannelClient; +import com.chartiq.finsemble.fdc3.context.Context; +import com.chartiq.finsemble.fdc3.meta.AppMetadata; +import com.chartiq.finsemble.interfaces.ConnectionEventGenerator; +import com.chartiq.finsemble.interfaces.ConnectionListener; +import com.fasterxml.jackson.core.JsonProcessingException; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.stage.Window; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class InteropServiceExample { + + /** + * Logger + */ + private static final Logger LOGGER = Logger.getLogger(InteropServiceExample.class.getName()); + + /** + * Arguments passed via the command line + */ + private List args; + + //region FXML controls + @FXML + private AnchorPane controlsPanel; + + @FXML + private Circle connectionStatus; + + @FXML + private Label connectionStatusLabel; + + @FXML + private Button getSystemChannelsButton; + + @FXML + private Button getCurrentChannelButton; + + @FXML + private TextField channelName; + + @FXML + private Button joinChannelButton; + + @FXML + private Button leaveChannelButton; + + @FXML + private Label currentChannelLabel; + + @FXML + private Button broadcastButton; + + @FXML + private Button getChannelCurrentContextButton; + + @FXML + private Button getCurrentChannelsButton; + + @FXML + private TextField applicationName; + + @FXML + private Button applicationNameButton; + + @FXML + private CheckBox openUseContext; + + @FXML + private TextField contextType; + + @FXML + private TextField contextName; + + @FXML + private TextField contextId; + + @FXML + private Button addContextListenerButton; + + @FXML + private Button removeContextListenerButton; + + @FXML + private TextField intent; + + @FXML + private CheckBox intentUseContext; + + @FXML + private Button findIntentButton; + + @FXML + private Button raiseIntentButton; + + @FXML + private Button addIntentListenerButton; + + @FXML + private Button removeIntentListenerButton; + + @FXML + private TextField channelApiChannelName; + + @FXML + private Button getOrCreateChannelButton; + + @FXML + private Button broadcastChannelApiButton; + + @FXML + private Button channelContextListener; + + @FXML + private Button getInfoButton; + + @FXML + private AnchorPane loggerPanel; + + @FXML + private Button clearMessagesButton; + + @FXML + private TextArea messages; + //endregion + + /** + * The finsemble connection + */ + private Finsemble fsbl; + + /** + * The window object managed by Finsemble + */ + private Window window; + + private FinsembleDesktopAgent finsembleDesktopAgent; + + private boolean windowReady = false; + + private Map listenersMap = new LinkedHashMap<>(); + + private ChannelClient getOrCreateChannel; + + /** + * Initializes a new instance of the JavaExample class. + */ + public InteropServiceExample() { + } + + /** + * Sets the arguments passed to Finsemble. + * + * @param args The arguments + */ + void setArguments(List args) { + this.args = args; + } + + /** + * Sets the window used by Finsemble for registration + * + * @param window The window + */ + void setWindow(Window window) { + this.window = window; + } + + @FXML + void addContextListener(ActionEvent event) { + String contextType = buildContext().getType(); + listenersMap.put(contextType, finsembleDesktopAgent.addContextListener(contextType, context -> { + appendMessage("*** Context Listener triggered ***"); + appendMessage(String.format("Context Id: %s\nContext type: %s", context.getId(), context.getType())); + appendMessage("*** --- ***"); + })); + } + + @FXML + void addIntentListener(ActionEvent event) { + String intentString = intent.getText(); + listenersMap.put(intentString, finsembleDesktopAgent.addIntentListener(intentString, context -> { + appendMessage("*** Intent Listener triggered ***"); + appendMessage(String.format("Context Id: %s\nContext type: %s", context.getId(), context.getType())); + appendMessage("*** --- ***"); + })); + } + + @FXML + void broadcast(ActionEvent event) { + finsembleDesktopAgent.broadcast(buildContext()); + } + + @FXML + void broadcastChannelApi(ActionEvent event) { + Context context = buildContext(); + getOrCreateChannel.broadcast(context); + } + + + @FXML + void findIntent(ActionEvent event) { + if (intentUseContext.isSelected()) { + finsembleDesktopAgent.findIntent(intent.getText(), buildContext()); + } else { + finsembleDesktopAgent.findIntent(intent.getText()); + } + } + + + @FXML + void getChannelCurrentContext(ActionEvent event) { + try { + Context context = finsembleDesktopAgent.channelGetCurrentContext(buildContext().getType()).get(); + appendMessage("getChannelCurrentContext: " + new JSONObject(context).toString(2)); + + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + + @FXML + void getInfo(ActionEvent event) throws ExecutionException, InterruptedException { + appendMessage("*** fdc3.getInfo() *** \n " + new JSONObject(finsembleDesktopAgent.getInfo() + .get()).toString(2)); + } + + @FXML + void getOrCreateChannel(ActionEvent event) throws ExecutionException, InterruptedException { + getOrCreateChannel = (ChannelClient) finsembleDesktopAgent.getOrCreateChannel(channelApiChannelName.getText()).get(); + appendMessage(String.format("getOrCreateChannel - %s", getOrCreateChannel.getId())); + } + + + @FXML + void raiseIntent(ActionEvent event) { + if (intentUseContext.isSelected()) { + finsembleDesktopAgent.raiseIntent(intent.getText(), buildContext()); + } else { + finsembleDesktopAgent.raiseIntent(intent.getText()); + } + } + + @FXML + void removeContextListener(ActionEvent event) { + String contextType = buildContext().getType(); + listenersMap.get(contextType).unsubscribe(); + } + + @FXML + void removeIntentListener(ActionEvent event) { + listenersMap.get(intent.getText()).unsubscribe(); + } + + @FXML + void getCurrentChannel(ActionEvent event) { + try { + ChannelClient channel = (ChannelClient) finsembleDesktopAgent.getCurrentChannel().get(); + String currentChannel = channel == null ? "None" : channel.getId(); + appendMessage("Current Channel: " + currentChannel); + currentChannelLabel.setText(currentChannel); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + @FXML + void getCurrentChannels(ActionEvent event) { + try { + List channels = finsembleDesktopAgent.getCurrentChannels().get(); + appendMessage(":: Current Channels ::"); + channels.forEach(channel -> appendMessage(((ChannelClient)channel).getId())); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + } + + @FXML + void getSystemChannels(ActionEvent event) { + finsembleDesktopAgent.getSystemChannels(); + } + + @FXML + void joinChannel(ActionEvent event) { + finsembleDesktopAgent.joinChannel(channelName.getText()); + } + + @FXML + void leaveChannel(ActionEvent event) { + finsembleDesktopAgent.leaveCurrentChannel(); + // TODO: This is if we want to fetch the current channel automatically after leaving + // otherwise we need to click the get current channel button +// try { +// List channels = finsembleDesktopAgent.getCurrentChannels().get(); +// appendMessage(":: Current Channels ::"); +// channels.forEach(channel -> appendMessage(((ChannelClient)channel).getId())); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } catch (ExecutionException e) { +// e.printStackTrace(); +// } + + // TODO: This is FDC2 2.0 +// finsembleDesktopAgent.leaveChannels(new String[] {"Channel 2", "Channel 3"}); + } + + @FXML + void openApplication(ActionEvent event) throws JsonProcessingException { + AppMetadata targetApp = new AppMetadata(); + targetApp.setAppId(applicationName.getText().trim()); + if (openUseContext.isSelected()) { + finsembleDesktopAgent.open(targetApp, buildContext()); + } else { + finsembleDesktopAgent.open(targetApp); + } + } + + + @FXML + void clearMessages(ActionEvent event) { + messages.clear(); + } + + + @FXML + void addChannelContextListener(ActionEvent event) { + Context context = buildContext(); + + getOrCreateChannel.addContextListener(context.getType(), context1 -> { + appendMessage("app channel context listener triggered: " + context1.toString()); + }); + } + + + private Context buildContext() { + Context context = new Context(); + context.setType(contextType.getText()); + if (contextId.getText() != null && !contextId.getText().isEmpty()) { + String[] idFields = contextId.getText().split(","); + Map contextId = new HashMap<>(); + for (String field : idFields) { + String[] f = field.split(":"); + contextId.put(f[0], f[1]); + } + context.setId(contextId); + } + context.setName(contextName.getText()); + + return context; + } + + + + /** + * Connect to Finsemble. + */ + void connect() { + if (fsbl != null) { + // Has already connected once, and reconnecting doesn't work. + return; + } + + messages.setEditable(false); + + // the following statement is used to log any messages + LOGGER.info(String.format("Starting JavaExample: %s", String.join(", ", args))); + + List windowSize = new LinkedList<>(); + windowSize.add("width=1100"); + windowSize.add("height=840"); + windowSize.add("componentType=JavaDesktopAgent"); + + setArguments(Stream.concat(args.stream(), windowSize.stream()) + .collect(Collectors.toList())); + fsbl = new Finsemble(args, window); + + try { + fsbl.connect(); + fsbl.addListener(new ConnectionListener() { + @Override + public void disconnected(ConnectionEventGenerator from) { + LOGGER.info("Finsemble connection closed"); + appendMessage("Finsemble connection closed"); + } + + @Override + public void error(ConnectionEventGenerator from, Exception e) { + LOGGER.log(Level.SEVERE, "Error from Finsemble", e); + } + + @Override + public void onWindowStateReady(ConnectionEventGenerator from) { + windowReady = true; + appendMessage("Window window state: READY"); + try { + appendMessage("Registering with InteropService..."); + registerWithInteropService(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + }); + + appendMessage("Window registered with Finsemble"); + + //GetComponentState + final JSONArray getComponentStateFields = new JSONArray() {{ + put("Finsemble_Linker"); + put("symbol"); + }}; + final JSONObject getComponentStateParam = new JSONObject() {{ + put("fields", getComponentStateFields); + }}; + // fsbl.getClients().getWindowClient().getComponentState(getComponentStateParam, this::handleGetComponentStateCb); + + + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "Error initializing Finsemble connection", ex); + appendMessage("Error initializing Finsemble connection: " + ex.getMessage()); + try { + fsbl.close(); + } catch (IOException e1) { + LOGGER.log(Level.SEVERE, "Error closing Finsemble connection", e1); + } + } + } + + /** + * Adds a message to the message box. + * + * @param s The message to add. + */ + void appendMessage(String s) { + if (messages != null) { + messages.appendText(String.format("\n%s", s)); + } + } + + private void registerWithInteropService() throws Exception { + + LOGGER.info("Finsemble Desktop Agent registration started..."); + finsembleDesktopAgent = new FinsembleDesktopAgent(fsbl, new DesktopAgentEvents() { + + @Override + public void onRegistrationFinished() { + LOGGER.info("Registered with the InteropService"); + appendMessage("Registered with the InteropService"); + Platform.runLater(new Runnable() { + @Override + public void run() { + setConnectionStatus(finsembleDesktopAgent.isRegistered()); + } + }); + + } + + @Override + public void onUnregister() { + LOGGER.info("Finsemble Desktop Agent unregistered"); + appendMessage("Finsemble Desktop Agent unregistered"); + Platform.runLater(new Runnable() { + @Override + public void run() { + setConnectionStatus(finsembleDesktopAgent.isRegistered()); + } + }); + } + + @Override + public void onMessageSent(String message) { + appendMessage(message); + } + + @Override + public void onMessageReceived(String message) { + appendMessage(message); + } + + @Override + public void onCurrentChannelChanged(ChannelClient channelClient) { + appendMessage("Current channel: " + channelClient.getId()); + Platform.runLater(new Runnable() { + @Override + public void run() { + currentChannelLabel.setText(channelClient.getId()); + } + }); + + } + }); + } + + private void setConnectionStatus(boolean isConnected) { + if (isConnected) { + connectionStatus.setFill(Color.LIMEGREEN); + connectionStatusLabel.setText("Connected"); + } else { + connectionStatus.setFill(Color.RED); + connectionStatusLabel.setText("Offline"); + } + } + + public Finsemble getFinsembleBridge() { + return fsbl; + } +} diff --git a/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExampleApplication.java b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExampleApplication.java new file mode 100644 index 0000000..081e4a9 --- /dev/null +++ b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceExampleApplication.java @@ -0,0 +1,164 @@ +/*********************************************************************************************************************** + Copyright 2018-2020 by ChartIQ, Inc. + Licensed under the ChartIQ, Inc. Developer License Agreement https://www.chartiq.com/developer-license-agreement + **********************************************************************************************************************/ +package com.chartiq.finsemble.example.fdc3; + +import com.chartiq.finsemble.example.JavaExampleApplication; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.stage.Window; +import javafx.stage.WindowEvent; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class InteropServiceExampleApplication extends Application { + /** + * Logger + */ + private static final Logger LOGGER = Logger.getLogger(InteropServiceExampleApplication.class.getName()); + + /** + * Arguments passed via the command line + */ + private static final InteropServiceMessageHandler messageHandler = new InteropServiceMessageHandler(); + + /** + * Initializes a new instance of the JavaExample class. + */ + public InteropServiceExampleApplication() { + } + + private InteropServiceExample controller; + + /** + * The main function of the application. + * + * @param args The command line arguments. + */ + public static void main(String[] args) { + final List argList = new ArrayList<>(Arrays.asList(args)); + + initLogging(argList); + + // the following statement is used to log any messages + LOGGER.info(String.format("Starting InteropServiceExampleApplication: %s", Arrays.toString(args))); + + launch(args); + + // the following statement is used to log any messages + LOGGER.info("Started InteropServiceExampleApplication"); + } + + //region JavaFX Application Implementation + + /** + * The main entry point for all JavaFX applications. + * The start method is called after the init method has returned, + * and after the system is ready for the application to begin running. + * + *

+ * NOTE: This method is called on the JavaFX Application Thread. + *

+ * + * @param primaryStage the primary stage for this application, onto which + * the application scene can be set. The primary stage will be embedded in + * the browser if the application was launched as an applet. + * Applications may create other stages, if needed, but they will not be + * primary stages and will not be embedded in the browser. + */ + @Override + public void start(Stage primaryStage) { + LOGGER.info("Start method called"); + + // Get arguments from Application + final List args = getParameters().getRaw(); + + LOGGER.info(String.format( + "Finsemble Java FDC3 Desktop Agent Example starting with arguments:\n\t%s", String.join("\n\t", args))); + final URL resource = JavaExampleApplication.class.getResource("InteropService.fxml"); + final FXMLLoader loader = new FXMLLoader(resource); + try { + final VBox anchorPane = loader.load(); + LOGGER.info("Parent loaded from resource"); + primaryStage.setTitle("InteropServiceExampleApplication"); + primaryStage.setResizable(false); + final Scene scene = new Scene(anchorPane, 265, 400); + + LOGGER.info("Scene created"); + primaryStage.setScene(scene); + + LOGGER.info("Showing window"); + primaryStage.show(); + + // Updated controller + controller = loader.getController(); + controller.setArguments(args); + final Window window = scene.getWindow(); + controller.setWindow(window); + messageHandler.setJavaExample(controller); + + final Thread connectThread = new Thread(controller::connect); + connectThread.setDaemon(true); + connectThread.start(); + + primaryStage.getScene().getWindow().addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, this::closeWindowEvent); + + LOGGER.info("Started successfully"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error in start", e); + } + } + //endregion + + private static void initLogging(List args) { + LOGGER.addHandler(messageHandler); + + if (System.getProperty("java.util.logging.config.file") != null) { + // Config file property has been set, no further initialization needed. + return; + } + + // Check whether logging file has been specified + final List properties = args + .stream() + .filter(arg -> arg.startsWith("-Djava.util.logging.config.file")) + .collect(Collectors.toList()); + + if ((properties.size() == 0) || !properties.get(0).contains("=")) { + // No logging properties specified + return; + } + + // Get filename from parameter + final String loggingPropertiesPath = properties.get(0).split("=")[1]; + try { + final InputStream inputStream = new FileInputStream(loggingPropertiesPath); + LogManager.getLogManager().readConfiguration(inputStream); + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, "Could not load default logging.properties file", e); + } + } + + + private void closeWindowEvent(WindowEvent event) { + try { + controller.getFinsembleBridge().getClients().getLoggerClient().close(); + } catch (IOException | NullPointerException e) { + LOGGER.warning(String.format("Error sending the unregister message: %s", e)); + } + } +} diff --git a/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceMessageHandler.java b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceMessageHandler.java new file mode 100644 index 0000000..ac632af --- /dev/null +++ b/src/main/java/com/chartiq/finsemble/example/fdc3/InteropServiceMessageHandler.java @@ -0,0 +1,86 @@ +package com.chartiq.finsemble.example.fdc3; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class InteropServiceMessageHandler extends Handler { + + private InteropServiceExample interopServiceExample; + + /** + * Sets the java example the handler should append messages to. + * @param interopServiceExample The Java example instance + */ + void setJavaExample(InteropServiceExample interopServiceExample) { + this.interopServiceExample = interopServiceExample; + } + + + /** + * Publish a LogRecord. + *

+ * The logging request was made initially to a Logger object, + * which initialized the LogRecord and forwarded it here. + *

+ * The Handler is responsible for formatting the message, when and + * if necessary. The formatting should include localization. + * + * @param record description of the log event. A null record is + * silently ignored and is not published + */ + @Override + public void publish(LogRecord record) { + if (interopServiceExample == null) { + return; + } + + final Throwable throwable = record.getThrown(); + + String stackTrace = ""; + if (throwable != null) { + final StringWriter sw = new StringWriter(); + sw.append("\n"); + + final PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + + stackTrace = sw.toString(); + } + + final String message = String.format( + "%s: %s.%s %s%s", + record.getLevel(), + record.getSourceClassName().substring(record.getSourceClassName().lastIndexOf(".")), + record.getSourceMethodName(), + record.getMessage(), + stackTrace); + + interopServiceExample.appendMessage(message); + } + + /** + * Flush any buffered output. + */ + @Override + public void flush() { + + } + + /** + * Close the Handler and free all associated resources. + *

+ * The close method will perform a flush and then close the + * Handler. After close has been called this Handler + * should no longer be used. Method calls may either be silently + * ignored or may throw runtime exceptions. + * + * @throws SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ + @Override + public void close() throws SecurityException { + + } +} diff --git a/src/main/resources/com/chartiq/finsemble/example/InteropService.fxml b/src/main/resources/com/chartiq/finsemble/example/InteropService.fxml new file mode 100644 index 0000000..c658f71 --- /dev/null +++ b/src/main/resources/com/chartiq/finsemble/example/InteropService.fxml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +