com.openfin.desktop.DesktopConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openfin-desktop-java-adapter Show documentation
Show all versions of openfin-desktop-java-adapter Show documentation
The Java API for OpenFin Runtime
package com.openfin.desktop;
import com.openfin.desktop.net.WebSocketConnection;
import com.openfin.desktop.net.WebSocketEventHandler;
import com.openfin.desktop.net.WebSocketException;
import com.openfin.desktop.net.WebSocketMessage;
import com.openfin.desktop.win32.DesktopPortHandler;
import com.openfin.desktop.win32.NamedPipePortHandler;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
import java.lang.System;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A object for launching, connecting to, and controlling Runtime.
*/
public class DesktopConnection {
private final static Logger logger = LoggerFactory.getLogger(DesktopConnection.class.getName());
private HashMap callbacks;
private HashMap notificationListenerMap = new HashMap();
// >>
private HashMap>>> applicationEventCallbackMap = new HashMap>>>();
// >
private HashMap>> systemEventCallbackMap = new HashMap>>();
// >>
private HashMap>>>> windowEventCallbackMap = new HashMap>>>>();
private List externalMessageHandlers = new ArrayList();
private WebSocketConnection websocket;
private JSONObject jsonMsg;
private boolean connected = false;
private String uuid, host;
private String path = "";
private Integer port; // if initial value is null, get it from Runtime based on Runtime version required
private int timeout; // current timeout value in seconds
private long waitStartTime; // start time of wait-to-connect thread
boolean opened = false;
boolean authRequested = false;
private String authorizationAction;
private String authorizationType;
private long messageId = 0;
private Integer notificationCount = 1;
private JSONObject notificationPayload;
private String runtimeSecurityRealm; // if speicified, adding "--security-realm=xxx" to Runtime argument
private List runtimeAppAssets; // if specified, used to defined appAssets
private String additionalRuntimeArguments; // additional Runtime arguments
private String rdmUrl; // RDM Url for license tracking
private int devtoolsPort; // port for chromium devtools
private String runtimeAssetsUrl; // URL for RVM and Runtime assets
private String additionalRvmArguments; // arguments to launch RVM, in addition to --config
private DesktopStateListener listener;
private PortDiscoveryHandler portDiscoveryHandler;
private java.util.Timer timer;
private boolean disconnecting = false;
private InterApplicationBus busInstance;
private long startTime;
private long eventTime;
private long authReqMsgId;
private EventListener portDiscoveryEventListener;
private RuntimeConfiguration activeConfiguration; // config used by current connection
/**
* Creates a new connection to Runtime
*
* @param uuid unique ID for Runtime to refer to this DesktopConnection
* @throws DesktopException if this method fails to create a connection
*/
public DesktopConnection(String uuid) throws DesktopException {
this(uuid, null, null);
}
/**
* Creates a new connection to Runtime
* @param uuid unique ID for Runtime to refer to this DesktopConnection
* @param host The host that Runtime is running on
* @param port The port that Runtime is listening on for connections. -null if unknown
* @throws DesktopException if this method fails to create a connection
* @deprecated use {@link #DesktopConnection(String)} instead.
*/
public DesktopConnection(String uuid, String host, Integer port) throws DesktopException {
if (uuid == null) {
throw new DesktopException("Invalid uuid: ");
}
this.uuid = uuid;
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
callbacks = new HashMap();
jsonMsg = new JSONObject();
busInstance = new InterApplicationBus(this);
}
private void timingEvent(String event) {
long now = System.currentTimeMillis();
double time = (now - eventTime)/1000d;
logger.debug("Event:" + event + " secs:" + time);
eventTime = now;
}
private void startEvent(String event) {
long now = System.currentTimeMillis();
double time = (now - startTime)/1000d;
logger.debug("Total Time: " + event + " secs:" + time);
}
private void sendRequestAuthorization() throws DesktopException, DesktopIOException {
if (!authRequested) {
JSONObject json = new JSONObject();
try {
json.put("action", authorizationAction); //"request-authorization");
JsonUtils.updateValue(json, "action", authorizationAction);
JSONObject client = new JSONObject();
client.put("type", "java");
client.put("version", OpenFinRuntime.getAdapterVersion() == null ? "N/A" : OpenFinRuntime.getAdapterVersion());
client.put("javaVersion", System.getProperty("java.version"));
JSONObject payload = new JSONObject();
payload.put("uuid", uuid);
payload.put("type", authorizationType); //"command-line-token");
payload.put("licenseKey", (this.activeConfiguration.getLicenseKey() == null || this.activeConfiguration.getLicenseKey().trim().isEmpty()) ? "N/A" : this.activeConfiguration.getLicenseKey());
payload.put("client", client);
payload.put("configUrl", this.activeConfiguration.getManifestLocation() != null ?
this.activeConfiguration.getManifestLocation() : this.activeConfiguration.getGeneratedManifestLocation());
payload.put("pid", getProcessId("N/A"));
json.put("payload", payload);
this.authReqMsgId = this.getNextMessageId();
json.put("messageId", this.authReqMsgId);
String msg = json.toString();
logger.debug("sending message: " + msg);
if (listener != null) listener.onOutgoingMessage(msg);
websocket.send(msg);
} catch (JSONException je) {
throw new DesktopException(je);
} catch (WebSocketException wex) {
throw new DesktopIOException(wex);
}
}
}
/**
* Launches Runtime and notifies the listener when connected. This method launches OpenFinRVM.exe embedded in the jar.
* @param commandLineArguments Command line arguments to start the Runtime with
* @param listener Receives updates on startup and connection state
* @param timeout For connecting to Desktop after launch. If the connection to Runtime is not established by the timeout the listener will get an onError() call
* @throws URISyntaxException if URL has invalid syntax
* @throws DesktopIOException if this method fails to connect to Runtime
* @deprecated use {@link #connect(RuntimeConfiguration, DesktopStateListener, int)} instead.
*/
public void launchAndConnect(String commandLineArguments, final DesktopStateListener listener, int timeout) throws URISyntaxException, DesktopIOException {
this.launchAndConnect(null, commandLineArguments, listener, timeout);
}
/**
* Launches Runtime and notifies the listener when connected.
* @param desktopPath Absolute path to the Runtime executable
* @param commandLineArguments Command line arguments to start the Runtime with
* @param listener Receives updates on startup and connection state
* @param timeout For connecting to Desktop after launch. If the connection to Runtime is not established by the timeout the listener will get an onError() call
* @throws DesktopIOException if this method fails to connect to Runtime
* @deprecated use {@link #connect(RuntimeConfiguration, DesktopStateListener, int)} instead.
*/
public void launchAndConnect(String desktopPath, String commandLineArguments, final DesktopStateListener listener, int timeout) throws DesktopIOException {
this.startTime = java.lang.System.currentTimeMillis();
this.eventTime = startTime;
this.listener = listener;
//make sure commandLineArguments isnt null...
commandLineArguments = commandLineArguments == null ? "" : commandLineArguments;
if (desktopPath != null) {
runDesktop(desktopPath, commandLineArguments);
} else {
RuntimeLauncher.launchConfig(commandLineArguments);
}
connect("file-token", listener);
}
/**
* Connect to specified version of Desktop. If the specified version is not running, this method will try to start it.
*
* @param runtimeVersion version of Runtime required
* @param listener Receives updates on startup and connection state
* @param timeout number of seconds to wait for connection. If connection to Runtime is not established by the timeout the listener will get an onError() call
* @throws DesktopIOException if this method fails to connect to Runtime
* @throws IOException if IO exception is thrown during launching Runtime
* @deprecated use {@link #connect(RuntimeConfiguration, DesktopStateListener, int)} instead.
*
*/
public void connectToVersion(final String runtimeVersion, final DesktopStateListener listener, final int timeout) throws DesktopIOException, IOException {
RuntimeConfiguration config = new RuntimeConfiguration();
config.setRuntimeVersion(runtimeVersion);
config.setSecurityRealm(this.runtimeSecurityRealm);
config.setAdditionalRuntimeArguments(this.additionalRuntimeArguments);
config.setRdmURL(this.rdmUrl);
config.setRuntimeAssetURL(this.runtimeAssetsUrl);
config.setAdditionalRvmArguments(this.additionalRvmArguments);
if (this.devtoolsPort > 0) {
config.setDevToolsPort(this.devtoolsPort);
}
connect(config, listener, timeout);
}
/**
* Connect to Runtime with the specified configuration. If the specified version is not running, this method will try to start it.
*
* @param configuration an instance of RuntimeConfiguration
* @param listener Receives updates on startup and connection state
* @param timeout number of seconds to wait for connection. If connection to Runtime is not established by the timeout the listener will get an onError() call
* @throws DesktopIOException if this method fails to connect to Runtime
* @throws IOException if IO exception is thrown during launching Runtime
* @see RuntimeConfiguration
*/
public void connect(final RuntimeConfiguration configuration, final DesktopStateListener listener, final int timeout) throws DesktopIOException, IOException {
if (configuration.getRuntimePort() > 0) {
logger.warn("connecting with hard-coded port has been deprecated. Please check API docs.");
this.port = configuration.getRuntimePort();
this.activeConfiguration = configuration;
this.connect(listener);
}
else if (this.portDiscoveryEventListener == null) {
this.startTime = java.lang.System.currentTimeMillis();
this.eventTime = startTime;
this.listener = listener;
this.port = null;
this.timeout = timeout;
this.activeConfiguration = configuration;
this.portDiscoveryEventListener = new EventListener() {
@Override
public void eventReceived(com.openfin.desktop.ActionEvent actionEvent) {
if (actionEvent.getType().equals("TIMEOUT")) {
logger.error("timed out on connectionToVersion");
portDiscoveryHandler.removeEventListener(portDiscoveryEventListener);
portDiscoveryEventListener = null;
listener.onError("Connection timed out");
} else {
JSONObject runtimeInfo = actionEvent.getEventObject();
// "port":7840,"requestedVersion":"stable","securityRealm":"noCacheRealm","sslPort":7840,"version":"5.44.7.17"}
Integer port = JsonUtils.getIntegerValue(runtimeInfo, "port", null);
String requestedVersion = JsonUtils.getStringValue(runtimeInfo, "requestedVersion", null);
String requestedSecurityRealm = JsonUtils.getStringValue(runtimeInfo, "securityRealm", null);
if (matchRuntimeInstance(requestedVersion, requestedSecurityRealm, configuration)) {
String runVersion = JsonUtils.getStringValue(runtimeInfo, "version", null);
if (port == null) {
listener.onError("Port for version " + configuration.getRuntimeVersion() + " not found in COPYDATA");
} else if (runVersion == null || runVersion.length() == 0) {
listener.onError("Version for version " + configuration.getRuntimeVersion() + " not found in COPYDATA");
} else if (DesktopConnection.this.port == null) {
DesktopConnection.this.port = port;
logger.debug("Runtime version " + runVersion + " at port " + port);
try {
DesktopConnection.this.connect(listener);
} catch (Exception e) {
logger.error("Error connecing to desktop", e);
listener.onError(e.getMessage());
}
} else {
logger.debug("Port already set at " + DesktopConnection.this.port + ", ignoring " + port);
}
portDiscoveryHandler.removeEventListener(portDiscoveryEventListener);
portDiscoveryEventListener = null;
} else {
logger.debug("RequestedVersion " + requestedVersion + " mismatches " + configuration.getRuntimeVersion() + ", ignoring...");
}
}
}
};
//if there is rvm --do-not-launch option, then no need to connect(do port discovery)
if (!configuration.isDoNotLaunch()) {
this.portDiscoveryHandler = initDesktopPortHandler(portDiscoveryEventListener, configuration);
} else {
logger.debug("isDoNotLaunch is true, skip port discovery");
}
RuntimeLauncher.launchVersion(configuration);
} else {
logger.error("Already waiting to connect to a version");
}
}
private boolean matchRuntimeInstance(String requestedVersion, String securityRealm, RuntimeConfiguration configuration) {
if (configuration.getRuntimeVersion() != null && configuration.getSecurityRealm() != null){
if (configuration.getRuntimeVersion().equals(requestedVersion) && configuration.getSecurityRealm().equals(securityRealm)) {
logger.debug("matched Runtime version and security realm");
return true;
}
}
if (configuration.getRuntimeFallbackVersion() != null && configuration.getSecurityRealm() != null){
if (configuration.getRuntimeFallbackVersion().equals(requestedVersion) && configuration.getSecurityRealm().equals(securityRealm)) {
logger.debug("matched Runtime fullback version and security realm");
return true;
}
}
if (configuration.getRuntimeVersion() != null && configuration.getSecurityRealm() == null) {
if (configuration.getRuntimeVersion().equals(requestedVersion) && securityRealm == null) {
logger.debug("matched Runtime version and null security realm");
return true;
}
}
if (configuration.getRuntimeFallbackVersion() != null && configuration.getSecurityRealm() == null) {
if (configuration.getRuntimeFallbackVersion().equals(requestedVersion) && securityRealm == null) {
logger.debug("matched Runtime fallback version and null security realm");
return true;
}
}
return false;
}
private PortDiscoveryHandler initDesktopPortHandler(EventListener portDiscoveryEventListener, RuntimeConfiguration runtimeConfiguration) {
PortDiscoveryHandler handler;
if (runtimeConfiguration.isUseNamedPipePortDiscovery() || !DesktopUtils.isWindows()) {
long randomNum = (long) Math.floor(Math.random() * 100000);
String pipeName = "OpenfinJavaAdapter." + System.currentTimeMillis() + "." + randomNum;
handler = DesktopUtils.isWindows() ? new com.openfin.desktop.win32.NamedPipePortHandler(pipeName) :
new com.openfin.desktop.nix.NamedPipePortHandler(pipeName);
runtimeConfiguration.setAdditionalRuntimeArguments("--v=1 --runtime-information-channel-v6=" +
handler.getEffectivePipeName());
handler.registerEventListener(portDiscoveryEventListener, this.timeout);
} else {
handler = DesktopPortHandler.getInstance();
handler.registerEventListener(portDiscoveryEventListener, this.timeout);
}
return handler;
}
/**
* Disconnects from Runtime
*
* @throws DesktopException if this method fails to disconnect from Runtime
*/
public void disconnect() throws DesktopException {
this.disconnect(null);
}
protected void disconnect(String reason) throws DesktopException {
disconnecting = true;
try {
if (timer != null) {
timer.cancel();
}
websocket.close(reason);
} catch (WebSocketException e) {
logger.error("Error disconnecting Runtime", e);
throw new DesktopException(e);
}
}
/**
* InterApplicationBus
* @return true if connected
*/
public boolean isConnected() {
return connected;
}
/**
* Notify Runtime to exit
*
* @throws DesktopException if this method fails to exit
*/
public void exit() throws DesktopException{
try {
sendAction("exit-desktop", new JSONObject());
} catch (Exception e) {
logger.error("Error existing Runtime", e);
throw new DesktopException(e);
}
}
/**
* Gets the Inter-Application message dispatcher associated with this DesktopConnection
* @return InterApplicationBus
*/
public InterApplicationBus getInterApplicationBus() {
return busInstance;
}
/**
* Gets port number that Runtime is running on
*
* @return port number
*/
public Integer getPort() {
return this.port;
}
/**
* Sends a message to Runtime
*
* @param action The action of the message
* @param payload The message object to send
* @throws DesktopException if this method fails to send the message
*/
public void sendAction(String action, JSONObject payload) throws DesktopException {
sendAction(action, payload, null);
}
/**
* Sends a message to Runtime
*
* @param action The action of the message
* @param payload The message object to send
* @param newMessageId message Id for the message object
* @throws DesktopException if this method fails to send the message
*/
private void sendAction(String action, JSONObject payload, Long newMessageId) throws DesktopException {
if (isConnected()) {
try {
jsonMsg.put("action", action);
if (newMessageId == null) {
newMessageId = this.getNextMessageId();
}
jsonMsg.put("messageId", newMessageId);
if (payload != null) {
jsonMsg.put("payload", payload);
} else {
jsonMsg.remove("payload");
}
String msg = jsonMsg.toString();
if (logger.isDebugEnabled()) {
logger.debug("Sending: " + msg);
}
if (listener != null) listener.onOutgoingMessage(msg);
websocket.send(msg);
} catch (Exception e) {
logger.error("Error sending action", e);
throw new DesktopException(e);
}
} else {
logger.warn("Not connected to sendAction " + jsonMsg);
}
}
/**
* Gets nesxt message Id
* @return next message Id
*/
private synchronized long getNextMessageId() {
return this.messageId++;
}
/**
* Sends a message to Runtime
* @param action The action of the message
* @param payload The message object to send
* @param listener AckListener for the message
* @param source Message source
* @see AckListener
*/
public void sendAction(String action, JSONObject payload, AckListener listener, Object source) {
if (isConnected()) {
ListenerSourcePair lsp = new ListenerSourcePair(listener, source);
Long msgId = null;
if (listener != null) {
msgId = this.getNextMessageId();
callbacks.put(msgId, lsp);
}
try {
sendAction(action, payload, msgId);
} catch (Exception ex) {
logger.error("Error sending action", ex);
DesktopUtils.errorAckOnException(listener, source, ex);
}
} else {
logger.warn("Not connected to sendAction " + action);
}
}
private void runDesktop(final String desktopPath, final String commandLine) throws DesktopIOException {
logger.debug("run desktop from " + desktopPath + " " + commandLine);
Thread thread = new Thread() {
public void run() {
try {
String line;
OutputStream stdin = null;
InputStream stderr = null;
InputStream stdout = null;
// launch EXE and grab stdin/stdout and stderr
Process process = Runtime.getRuntime().exec("cmd");
stdin = process.getOutputStream();
stderr = process.getErrorStream();
stdout = process.getInputStream();
// "write" the parms into stdin
line = "start " + desktopPath + " " + commandLine + "\n";
logger.debug(line);
stdin.write(line.getBytes());
stdin.flush();
stdin.close();
// clean up if any output in stdout
BufferedReader brCleanUp =
new BufferedReader(new InputStreamReader(stdout));
while ((line = brCleanUp.readLine()) != null) {
logger.debug("[Stdout] " + line);
}
brCleanUp.close();
// clean up if any output in stderr
brCleanUp =
new BufferedReader(new InputStreamReader(stderr));
while ((line = brCleanUp.readLine()) != null) {
logger.debug("[Stderr] " + line);
}
brCleanUp.close();
} catch (Exception e1) {
logger.error("Error starting Runtime", e1);
}
}
};
thread.setName(this.getClass().getName() + ".runDesktop");
thread.start();
}
/**
* Connects to an Runtime process
*
* @param listener Receives updates on startup and connection state
* @see DesktopStateListener
*
* @deprecated use {@link #connect(RuntimeConfiguration, DesktopStateListener, int)} instead.
*/
public void connect(final DesktopStateListener listener) {
if (this.activeConfiguration == null) {
this.activeConfiguration = new RuntimeConfiguration(); // fake to avoid NullPointerException
}
this.connect("file-token", listener);
}
/**
* Connects to an Runtime process
*
* @param type Describes the type of connection to establish
* @param listener Receives updates on startup and connection state
* @see DesktopStateListener
*/
private void connect(final String type, final DesktopStateListener listener) {
if (this.connected) {
listener.onError("Desktop websocket already connected");
return;
}
this.authorizationAction = "request-external-authorization";
this.authorizationType = type;
this.authRequested = false;
try {
if (host == null || path == null) throw new WebSocketException("");
URI uri = new URI("ws://" + host + ":" + port + "/" + path);
logger.debug("opening websocket " + uri.toString());
websocket = new WebSocketConnection(uri); // Register Event Handlers
} catch (Exception ex) {
logger.error("Error connecting to Runtime", ex);
listener.onError(ex.getMessage());
}
websocket.setEventHandler(new WebSocketEventHandler() {
public void onOpen() {
try {
if (activeConfiguration.getMaxMessageSize() > 0) {
logger.debug(String.format("setting max message size %d", activeConfiguration.getMaxMessageSize()));
websocket.setMaxMessageSize(activeConfiguration.getMaxMessageSize());
}
timingEvent("onOpen");
disconnecting = false;
opened = true;
cancelConnectWaitTimer();
sendRequestAuthorization();
} catch (Exception authEx) {
logger.error("Error onOpen", authEx);
listener.onError(authEx.getMessage());
}
}
public void onMessage(WebSocketMessage message) {
if (logger.isDebugEnabled()) {
logger.debug("Received message: " + message.getText());
}
listener.onMessage(message.getText());
try {
JSONObject json = new JSONObject(message.getText());
String action = json.getString("action");
JSONObject payload = json.getJSONObject("payload");
timingEvent("action:" + action);
if (action.equals("ack")) {
logger.debug("Receiving Ack: " + message.getText());
if (json.has("correlationId")) {
long correlationId = json.getLong("correlationId");
if (correlationId == DesktopConnection.this.authReqMsgId) {
boolean authDone = payload.getBoolean("success");
if (!authDone) {
logger.debug("retry sendRequestAuthorization");
try {
sendRequestAuthorization();
} catch (Exception authEx) {
logger.error("Error sending auth request", authEx);
listener.onError(authEx.getMessage());
}
}
} else {
fireMessageCallback(correlationId, payload);
}
}
}
else if (action.equals("external-authorization-response"))
{
try {
processExternalAuthorizationResponse(payload);
} catch (Exception authEx) {
logger.error("Error processing auth request", authEx);
listener.onError(authEx.getMessage());
}
}
else if (action.equals("authorization-response")) {
authRequested = true;
boolean success = payload.getBoolean("success");
if (success) {
connected = true;
listener.onReady();
startEvent("Connected");
} else {
if (!disconnecting) {
String reason = payload.getString("reason");
try {
listener.onError(reason);
disconnect("Authorization request to Runtime is denied");
} catch (Exception ex) {
logger.error("Error processing auth response");
}
}
}
}
else if (action.equals("process-message")) {
String sourceUuid = payload.getString("sourceUuid");
String topic = payload.getString("topic");
busInstance.dispatchMessageToCallbacks(sourceUuid, topic, payload.get("message"));
if ("utils-ready".equals(topic)) {
startEvent("AdminReady");
}
}
else if (action.equals("process-notification-event"))
{
processNotificationEvent(payload);
}
else if (action.equals("process-desktop-event"))
{
dispatchDesktopEvent(payload);
}
else if (action.equals("subscriber-added")) {
busInstance.dispatchToSubscribeListeners(payload.getString("uuid"), payload.getString("topic"));
}
else if (action.equals("subscriber-removed")) {
busInstance.dispatchToUnsubscribeListeners(payload.getString("uuid"), payload.getString("topic"));
}
else if (action.equals("process-external-app-action")) {
// processExternalAppAction(payload);
}
else if (action.equals("ping")) {
long pingId = payload.getLong("pingId");
respondToPing(pingId);
}
else if (action.equals("process-external-message")) {
processExternalMessage(payload);
}
else if (action.equals("app-connected")) {
processWindowConnected(payload);
}
else if (action.equals("app-loaded")) {
processWindowLoaded(payload);
}
} catch (JSONException e) {
logger.error("Error processing message from Runtime", e);
listener.onError(e.getMessage());
}
}
public void onClose() {
logger.debug("onClose");
connected = false;
opened = false;
cancelConnectWaitTimer();
listener.onClose();
disconnecting = false;
}
});
startConnectWaitTimer(new TimerTask() {
@Override
public void run() {
if (websocket.isConnected()) {
logger.debug("connectToDesktop-timer-stopped connected");
timer.cancel();
}
else if (authRequested) {
logger.debug("connectToDesktop-timer-stopped auth received");
timer.cancel();
}
else if (opened) {
// should be called in WebSocketEventHandler
// sendRequestAuthorization();
} else {
try {
logger.debug("Trying to connect to Runtime");
websocket.connect();
} catch (WebSocketException e) {
logger.error("Failed to connect to Runtime", e);
}
}
if (checkConnectTimeout()) {
cancelConnectWaitTimer();
listener.onError("Connection timed out");
} }
});
}
private void startConnectWaitTimer(TimerTask timerTask) {
cancelConnectWaitTimer();
this.waitStartTime = System.currentTimeMillis();
timer = new java.util.Timer(String.format("ConnectWaitTimer-%s", this.uuid));
logger.debug("connectToDesktop-timer-start " + uuid);
timer.schedule(timerTask, 0, 100);
}
private void cancelConnectWaitTimer() {
if (timer != null) {
logger.debug("connectToDesktop-timer-stopped " + this.uuid);
timer.cancel();
}
}
private boolean checkConnectTimeout() {
int secs = (int) ((System.currentTimeMillis() - this.waitStartTime) / 1000);
if (secs > this.timeout) {
logger.debug("checkConnectTimeout true " + this.uuid);
return true;
} else {
return false;
}
}
private void fireMessageCallback(long correlationId, JSONObject payload) {
ListenerSourcePair lsp = callbacks.remove(correlationId);
if (lsp != null) {
Object source = lsp.getSource();
AckListener ackListener = lsp.getListener();
Ack ack = new Ack(payload, source);
if (ack.isSuccessful()) {
DesktopUtils.successAck(ackListener, ack);
} else {
DesktopUtils.errorAck(ackListener, ack);
}
}
}
private void processExternalAuthorizationResponse(JSONObject payload) throws JSONException, IOException, WebSocketException {
// file-token authorization requested. Must write token into file.
if (authorizationType.equals("file-token"))
{
String token = payload.getString("token");
String file = payload.getString("file");
if (token != null &&
file != null &&
file.length() > 0 &&
token.length() > 0)
{
FileWriter writer = new FileWriter(file);
writer.write(token);
writer.close();
}
}
sendFinalAuthorizationRequest(authorizationType);
}
private void sendFinalAuthorizationRequest(String type) throws JSONException, WebSocketException {
disconnecting = false;
JSONObject json = new JSONObject();
json.put("action", "request-authorization");
JSONObject payload = new JSONObject();
payload.put("uuid", uuid);
payload.put("type", type);
if (this.activeConfiguration.isNonPersistent()) {
payload.put("nonPersistent", true);
}
json.put("payload", payload);
String msg = json.toString();
if (listener != null)
{
listener.onOutgoingMessage(msg);
}
websocket.send(msg);
}
private void processNotificationEvent(JSONObject payload)
{
if (payload != null)
{
Integer notificationId = getNestedJSONInteger(payload, "payload.notificationId");
if (notificationId != null) {
try {
ListenerSourcePair lsp = notificationListenerMap.get(notificationId);
if (lsp != null)
{
String type = payload.getString("type");
if (lsp.getNotificationListener() != null) {
invokeNotificationCallback(lsp.getNotificationListener(), new Ack(payload, lsp.getSource()), type);
}
// Remove if the notification is no longer being used by Runtime.
if (type == "close" ||
type == "error" ||
type == "dismiss") {
notificationListenerMap.remove(notificationId);
}
}
} catch (Exception e) {
logger.error("Error processing notification event", e);
}
}
}
}
private void invokeNotificationCallback(NotificationListener listener, Ack ack, String type) {
// convert click to onClick
String methodName = "on" + type.substring(0, 1).toUpperCase() + type.substring(1);
try {
Method method = listener.getClass().getMethod(methodName, ack.getClass());
if (method != null) {
method.setAccessible(true);
method.invoke(listener, ack);
}
} catch (Exception e) {
logger.error("Error invoking notification callback", e);
}
}
/**
* Registers listener for a new notification
* @param listener NotificationListener for the notification
* @param source Source of the request
* @return Notification Id
* @see NotificationListener
*/
public Integer registerNotificationListener(NotificationListener listener, Object source) {
Integer returnId = 0;
if(listener != null) {
notificationListenerMap.put(notificationCount, new ListenerSourcePair(listener, source));
returnId = notificationCount++;
}
return returnId;
}
public void sendActionToNotificationsCenter(String action, JSONObject payload, AckListener callback, Object source)
{
try {
if(notificationPayload == null) {
notificationPayload = new JSONObject();
}
notificationPayload.put("action", action);
notificationPayload.put("payload", payload);
sendAction("send-action-to-notifications-center", notificationPayload, callback, this);
} catch (Exception e) {
logger.error("Error sending message to notification center", e);
DesktopUtils.errorAckOnException(callback, payload, e);
}
}
private Integer getNestedJSONInteger(JSONObject json, String expression) {
Integer result = null;
StringTokenizer tokenizer = new StringTokenizer(expression, ".");
List list = new ArrayList();
while (tokenizer.hasMoreTokens()) {
list.add(tokenizer.nextToken());
}
JSONObject nested = json;
try {
for (int i = 0; i < list.size() - 1; i++) {
if (nested.has(list.get(i))) {
nested = nested.getJSONObject(list.get(i));
} else {
nested = null;
break;
}
}
if (nested != null) {
result = nested.getInt(list.get(list.size() - 1));
}
} catch (Exception e) {
logger.error("Error reading integer from JSON", e);
}
return result;
}
private List getEventCallbackList(JSONObject subscriptionObject) {
List list = null;
try {
String topic = subscriptionObject.getString("topic");
String type = getFullEventType(topic, subscriptionObject.getString("type"));
String uuid = JsonUtils.getStringValue(subscriptionObject, "uuid", null);
String name = null;
if (subscriptionObject.has("name")) {
name = subscriptionObject.getString("name");
}
list = getEventCallbackList(topic, type, uuid, name);
} catch (Exception e) {
logger.error("Error getting event callback list", e);
}
return list;
}
private List getEventCallbackList(String topic, String type, String uuid, String name) {
List callbacks = null;
if(topic != null && type != null) {
if (topic.equals("application")) {
if (uuid != null)
{
HashMap>> matchedTopic = applicationEventCallbackMap.get(topic);
if (matchedTopic == null) {
matchedTopic = new HashMap>>();
applicationEventCallbackMap.put(topic, matchedTopic);
}
HashMap> matchedType = matchedTopic.get(type);
if (matchedType == null) {
matchedType = new HashMap>();
matchedTopic.put(type, matchedType);
}
callbacks = matchedType.get(uuid);
if (callbacks == null) {
callbacks = new ArrayList();
matchedType.put(uuid, callbacks);
}
}
}
else if (topic.equals("system")) {
HashMap> matchedTopic = systemEventCallbackMap.get(topic);
if(matchedTopic == null) {
matchedTopic = new HashMap>();
systemEventCallbackMap.put(topic, matchedTopic);
}
callbacks = matchedTopic.get(type);
if (callbacks == null) {
callbacks = new ArrayList();
matchedTopic.put(type, callbacks);
}
}
else if (topic.equals("window")) {
if (uuid != null && name != null) {
HashMap>>> matchedTopic = windowEventCallbackMap.get(topic);
if(matchedTopic == null) {
matchedTopic = new HashMap>>>();
windowEventCallbackMap.put(topic, matchedTopic);
}
HashMap>> matchedType = matchedTopic.get(type);
if(matchedType == null) {
matchedType = new HashMap>>();
matchedTopic.put(type, matchedType);
}
HashMap> matchedUuid = matchedType.get(uuid);
if(matchedUuid == null) {
matchedUuid = new HashMap>();
matchedType.put(uuid, matchedUuid);
}
callbacks = matchedUuid.get(name);
if (callbacks == null) {
callbacks = new ArrayList();
matchedUuid.put(name, callbacks);
}
}
} else {
callbacks = null;
}
}
return callbacks;
}
private String getFullEventType(String topic, String type) {
String fullType;
if (topic.equals("application")) {
fullType = (type.indexOf("application-") == -1) ? ("application-" + type) : type;
}
else if (topic.equals("system")) {
fullType = type;
}
else if (topic.equals("window")) {
fullType = (type.indexOf("window-") == -1) ? ("window-" + type) : type;
} else {
fullType = type;
}
return fullType;
}
/**
* Registers an event listener on the specified event
* @param subscriptionObject JSON object containing subscription information such as the topic and type
* @param listener EventListener for the event
* @param callback AckListener for this request
* @param source Source of this request
* @see EventListener
* @see AckListener
*/
public void addEventCallback(JSONObject subscriptionObject,
EventListener listener,
AckListener callback,
Object source) {
List callbacks = getEventCallbackList(subscriptionObject);
if (callbacks != null) {
// Notify core of first subscription
if (callbacks.size() == 0) {
String topic = subscriptionObject.getString("topic");
String type = subscriptionObject.getString("type");
if (topic.equals("window") && type.equals("app-connected")) {
// app-connected is generated differently from other window events
JSONObject notifyConnected = new JSONObject();
notifyConnected.put("targetUuid", subscriptionObject.getString("uuid"));
notifyConnected.put("name", subscriptionObject.getString("name"));
sendAction("notify-on-app-connected", notifyConnected, callback, source);
}
else if (topic.equals("window") && type.equals("app-loaded")) {
// app-loaded is generated differently from other window events
JSONObject notifyConnected = new JSONObject();
notifyConnected.put("targetUuid", subscriptionObject.getString("uuid"));
notifyConnected.put("name", subscriptionObject.getString("name"));
sendAction("notify-on-content-loaded", notifyConnected, callback, source);
}
else {
sendAction("subscribe-to-desktop-event", subscriptionObject, callback, source);
}
}
try {
callbacks.add(new ListenerSourcePair(listener, subscriptionObject.getString("type"), source));
if (callbacks.size() > 1) {
// did not call sendAction so need to ack right away
if (callback != null) {
JSONObject payload = new JSONObject();
payload.put("success", true);
DesktopUtils.successAck(callback, new Ack(payload, source));
}
}
} catch (Exception e) {
logger.error("Error adding event callback", e);
}
}
}
/**
* Removes a previously registered event listener from the specified event
* @param subscriptionObject JSON object containing subscription information such as the topic and type
* @param listener EventListener that was registered before
* @param callback AckListener for this request
* @param source source of this request
*/
public void removeEventCallback(JSONObject subscriptionObject,
EventListener listener,
AckListener callback,
Object source) {
List callbacks = getEventCallbackList(subscriptionObject);
if (callbacks != null) {
removeFromListenerSourcePairList(callbacks, listener, source);
// Notify core if no subscriptions remain
if (callbacks.size() == 0) {
String topic = subscriptionObject.getString("topic");
String type = subscriptionObject.getString("type");
if (topic.equals("window") && type.equals("app-connected")) {
// core does not support remove app-connected listeners
}
else if (topic.equals("window") && type.equals("app-loaded")) {
// core does not support remove app-loaded listeners
}
else {
sendAction("unsubscribe-to-desktop-event", subscriptionObject, callback, source);
}
}
}
}
private void removeFromListenerSourcePairList(List list, EventListener listener, Object source) {
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ListenerSourcePair lsp = list.get(i);
if (lsp.getEventListener() == listener && lsp.getSource() == source) {
list.remove(i);
break;
}
}
}
}
private void dispatchDesktopEvent(JSONObject payload) {
List callbacks = getEventCallbackList(payload);
if (callbacks != null) {
for (ListenerSourcePair lsp : callbacks) {
lsp.getEventListener().eventReceived(new com.openfin.desktop.ActionEvent(lsp.getType(), payload, lsp.getSource()));
}
}
}
protected void respondToPing(long pingId) {
JSONObject payload = new JSONObject();
try {
payload.put("correlationId", pingId);
sendAction("pong", payload);
} catch (Exception e) {
logger.error("Error responding to ping", e);
}
}
/**
* Registers a listener to handle messages for this connection's UUID originating via HTTPS/HTTP
* @param listener process a received HTTPS/HTTP message for this connection
* @param source The object that originally registered the listener
*/
public void addExternalMessageHandler(ExternalMessageListener listener, Object source) {
externalMessageHandlers.add(new ExternalMessageListenerSourcePair(listener, source));
}
/**
*
* Dispatches the a payload from "process-external-message" to all registered ExternalMessageHandlerDelegates
* and sends the result after all result handlers have set a success/fail result.
*
* @param payload The data to process
*/
private void processExternalMessage(JSONObject payload) {
ExternalMessageResultHandlerFactory messageHandlerFactory = new ExternalMessageResultHandlerFactory(uuid, this, payload);
for (ExternalMessageListenerSourcePair pair : this.externalMessageHandlers) {
pair.handler.process(messageHandlerFactory.makeResultHandler(), payload);
}
messageHandlerFactory.allDispatched();
}
/**
* Dispatches event to app-connected listeners
*
* @param payload
*/
private void processWindowConnected(JSONObject payload) {
// app-connected is not generated as an event of window type
// make it a window event
String uuid = payload.getString("uuid"); // this is not the appUUID
String appUuid = payload.getString("appUuid");
String name = payload.getString("name"); // window name
String topic = "window";
String type = getFullEventType(topic, "app-connected");
List list = this.getEventCallbackList(topic, type, appUuid, name);
if (list != null) {
for (ListenerSourcePair pair: list) {
pair.getEventListener().eventReceived(new com.openfin.desktop.ActionEvent(pair.getType(), payload, pair.getSource()));
}
}
}
/**
* Dispatches event to app-loadedlisteners
*
* @param payload
*/
private void processWindowLoaded(JSONObject payload) {
// app-loaded is not generated as an event of window type
// make it a window event
String uuid = payload.getString("uuid"); // this is not the appUUID
String appUuid = payload.getString("appUuid");
String name = payload.getString("name"); // window name
String topic = "window";
String type = getFullEventType(topic, "app-loaded");
List list = this.getEventCallbackList(topic, type, appUuid, name);
if (list != null) {
for (ListenerSourcePair pair: list) {
pair.getEventListener().eventReceived(new com.openfin.desktop.ActionEvent(pair.getType(), payload, pair.getSource()));
}
}
}
/**
*
* Set additional runtime arguments passed to the Runtime
*
* @param additionalRuntimeArguments additional arguments, such as "--v=1" to enable more logging
* @deprecated use {@link RuntimeConfiguration} instead
*
*/
public void setAdditionalRuntimeArguments(String additionalRuntimeArguments) {
this.additionalRuntimeArguments = additionalRuntimeArguments;
}
/**
*
* Set additional runtime arguments passed to the RVM
*
* @param additionalRvmArguments additional arguments
* @deprecated use {@link RuntimeConfiguration} instead
*
*/
public void setAdditionalRvmArguments(String additionalRvmArguments) {
this.additionalRvmArguments = additionalRvmArguments;
}
/**
*
* Set security realm of the Runtime
* @param runtimeSecurityRealm name of the realm
* @deprecated use {@link RuntimeConfiguration} instead
*
*/
public void setRuntimeSecurityRealm(String runtimeSecurityRealm) {
this.runtimeSecurityRealm = runtimeSecurityRealm;
}
/**
* Set URL of RDM service
*
* @param url URL of RDM service
* @deprecated use {@link #connect(RuntimeConfiguration, DesktopStateListener, int)} instead.
* @deprecated use {@link RuntimeConfiguration} instead
*/
public void setRdmUrl(String url) {
this.rdmUrl = url;
}
/**
* Set port for accessing devtools on localhost
* @param port port number
* @deprecated use {@link RuntimeConfiguration} instead
*/
public void setDevToolsPort(int port) {
this.devtoolsPort = port;
}
/**
* Set root URL/URI for the RVM and Runtime assets. This can be a CDN or other server location.
*
* RVM and Runtime assets should be served from the following paths:
*
*
* url/rvm
* url/runtime
*
*
* @param url URL of Runtime assets
* @deprecated use {@link RuntimeConfiguration} instead
*/
public void setRuntimeAssetsUrl(String url) {
this.runtimeAssetsUrl = url;
}
/**
* Add a config for app asset
*
* @param config config string which must be a valid JSON string
* @deprecated use {@link RuntimeConfiguration} instead
*/
public void addAppAsset(String config) {
if (this.runtimeAppAssets == null) {
this.runtimeAppAssets = new ArrayList();
}
this.runtimeAppAssets.add(config);
}
/**
* Set log level to be FINE
*
* @deprecated
* @param enabled true for enabling DEBUG logging
*
* @deprecated use logging configuration file instead
*/
public void setLogLevel(boolean enabled) {
}
private static class ListenerSourcePair {
private AckListener listener;
private EventListener eventListener;
private NotificationListener notificationListener;
private Object source;
private String type;
ListenerSourcePair(AckListener listener, Object source) {
this.listener = listener;
this.source = source;
}
ListenerSourcePair(NotificationListener listener, Object source) {
this.notificationListener = listener;
this.source = source;
}
ListenerSourcePair(EventListener listener, String eventType, Object source) {
this.eventListener = listener;
this.type = eventType;
this.source = source;
}
Object getSource() {
return source;
}
AckListener getListener() {
return listener;
}
NotificationListener getNotificationListener() {
return this.notificationListener;
}
EventListener getEventListener() {
return this.eventListener;
}
String getType() {
return this.type;
}
}
private static class ExternalMessageListenerSourcePair {
private ExternalMessageListener handler;
private Object source;
public ExternalMessageListenerSourcePair(ExternalMessageListener handler, Object source) {
this.handler = handler;
this.source = source;
}
}
private static String getProcessId(final String fallback) {
// Note: may fail in some JVM implementations
// therefore fallback has to be provided
// something like '@', at least in SUN / Oracle JVMs
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
final int index = jvmName.indexOf('@');
if (index < 1) {
// part before '@' empty (index = 0) / '@' not found (index = -1)
return fallback;
}
try {
return Long.toString(Long.parseLong(jvmName.substring(0, index)));
} catch (NumberFormatException e) {
// ignore
}
return fallback;
}
}