org.fxconnector.remote.RemoteConnectorImpl Maven / Gradle / Ivy
The newest version!
/*
* Scenic View,
* Copyright (C) 2012 Jonathan Giles, Ander Ruiz, Amy Fowler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.fxconnector.remote;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import org.fxconnector.*;
import org.fxconnector.details.DetailPaneType;
import org.fxconnector.event.FXConnectorEvent;
import org.fxconnector.event.FXConnectorEventDispatcher;
import org.fxconnector.node.SVNode;
import org.scenicview.utils.ExceptionLogger;
import org.scenicview.utils.Logger;
import org.scenicview.utils.Platform;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
class RemoteConnectorImpl extends UnicastRemoteObject implements RemoteConnector, FXConnector {
private static final long serialVersionUID = -8263538629805832734L;
private final Map vmInfo = new HashMap<>();
private final Map applications = new HashMap<>();
private FXConnectorEventDispatcher dispatcher;
private final List previous = new ArrayList<>();
private List apps;
private final AtomicInteger count = new AtomicInteger();
private final int port;
private final List attachError = new ArrayList<>();
private File agentFile;
RemoteConnectorImpl() throws RemoteException {
super();
this.port = getValidPort();
RMIUtils.bindScenicView(this, port);
}
@Override
public void dispatchEvent(final FXConnectorEvent event) {
if (dispatcher != null) {
javafx.application.Platform.runLater(() -> {
synchronized (previous) {
if (!previous.isEmpty()) {
for (FXConnectorEvent fxConnectorEvent : previous) {
dispatcher.dispatchEvent(fxConnectorEvent);
}
previous.clear();
}
}
dispatcher.dispatchEvent(event);
});
} else {
synchronized (previous) {
previous.add(event);
}
}
}
@Override
public void onAgentStarted(final int port) {
Logger.print("Remote agent started on port:" + port);
RMIUtils.findApplication(port, application -> {
applications.put(vmInfo.get(port), application);
try {
final int appsID = Integer.parseInt(vmInfo.get(port));
final StageID[] ids = application.getStageIDs();
addStages(appsID, ids, application);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
count.decrementAndGet();
});
}
private void addStages(final int appsID, final StageID[] ids, final RemoteApplication application) {
final AppControllerImpl impl = new AppControllerImpl(appsID, Integer.toString(appsID)) {
@Override
public void close() {
super.close();
try {
application.close();
} catch (final RemoteException e) {
// Nothing to do
}
}
};
for (int i = 0; i < ids.length; i++) {
Logger.print("RemoteApp connected on:" + port + " stageID:" + ids[i]);
final int cont = i;
impl.getStages().add(new StageController() {
final StageID id = new StageID(appsID, ids[cont].getStageID());
private boolean isOpened;
{
id.setName(ids[cont].getName());
}
@Override
public StageID getID() {
return id;
}
@Override
public void update() {
try {
application.update(getID());
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void configurationUpdated(final Configuration configuration) {
try {
application.configurationUpdated(getID(), configuration);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void close() {
try {
isOpened = false;
application.close(getID());
} catch (final ConnectException e2) {
// Nothing to do
} catch (final Exception e) {
ExceptionLogger.submitException(e);
}
}
@Override
public boolean isOpened() {
return isOpened;
}
@Override
public void setEventDispatcher(final FXConnectorEventDispatcher dispatcher) {
isOpened = true;
RemoteConnectorImpl.this.dispatcher = dispatcher;
try {
application.setEventDispatcher(getID(), null);
} catch (final RemoteException e) {
// TODO Auto-generated catch block
ExceptionLogger.submitException(e);
}
}
@Override
public void setSelectedNode(final SVNode value) {
try {
application.setSelectedNode(getID(), value);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void removeSelectedNode() {
try {
application.removeSelectedNode(getID());
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public AppController getAppController() {
return impl;
}
@Override
public void setDetail(final DetailPaneType detailType, final int detailID, final String value) {
try {
application.setDetail(getID(), detailType, detailID, value);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void animationsEnabled(final boolean enabled) {
try {
application.animationsEnabled(getID(), enabled);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void updateAnimations() {
try {
application.updateAnimations(getID());
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
@Override
public void pauseAnimation(final int animationID) {
try {
application.pauseAnimation(getID(), animationID);
} catch (final RemoteException e) {
ExceptionLogger.submitException(e);
}
}
});
}
if (!impl.getStages().isEmpty()) {
apps.add(impl);
} else {
/**
* Keep the agent connected
*/
}
}
/**
* This method is periodically call to connect to remote VM that may have JavaFX Application running on them
*/
@Override
public List connect() {
apps = new ArrayList<>();
vmInfo.clear();
final List machines = getRunningJavaFXApplications();
Logger.print(machines.size() + " JavaFX applications found");
count.set(machines.size());
if (agentFile == null) {
agentFile = findAgent();
}
try {
final List validIDs = new ArrayList<>();
for (final VirtualMachine machine : machines) {
validIDs.add(machine.id());
final VirtualMachine temp = machine;
boolean connected = false;
if (applications.containsKey(temp.id())) {
final RemoteApplication application = applications.get(temp.id());
try {
final int appsID = Integer.parseInt(temp.id());
final StageID[] ids = application.getStageIDs();
addStages(appsID, ids, application);
connected = true;
count.decrementAndGet();
} catch (final Exception e) {
ExceptionLogger.submitException(e, "Failure connecting to machine.");
applications.remove(temp.id());
}
}
if (!connected) {
Thread agentThread = new Thread() {
{
setDaemon(true);
}
@Override
public void run() {
loadAgent(temp, agentFile);
}
};
agentThread.start();
}
}
/**
* Remove obsolete VM
*/
for (Iterator iterator = applications.keySet().iterator(); iterator.hasNext(); ) {
String ids = (String) iterator.next();
if (!validIDs.contains(ids)) {
iterator.remove();
}
}
} catch (final Exception e) {
ExceptionLogger.submitException(e);
}
final long initial = System.currentTimeMillis();
/**
* MAC Seems to be slower using attach API
*/
final long timeout = Platform.getCurrent() == Platform.OSX ? 30000 : 10000;
while (count.get() != 0 && System.currentTimeMillis() - initial < timeout) {
try {
Thread.sleep(50);
} catch (final InterruptedException e) {
// no-op
}
}
Logger.setEnabled(false);
return apps;
}
@Override
public void close() {
try {
RMIUtils.unbindScenicView(port);
} catch (final Exception e) {
ExceptionLogger.submitException(e);
}
}
private int getValidPort() {
int port = RMIUtils.getClientPort();
boolean valid = false;
do {
try {
final Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", port), 100);
socket.close();
valid = true;
port = RMIUtils.getClientPort();
} catch (final Exception e) {
valid = false;
}
} while (valid);
return port;
}
private void loadAgent(final VirtualMachine machine, final File agentFile) {
try {
final long start = System.currentTimeMillis();
final int port = getValidPort();
Logger.print("Loading agent for:" + machine + " ID:" + machine.id() + " on port:" + port + " took:" + (System.currentTimeMillis() - start) + "ms using agent defined in " + agentFile.getAbsolutePath());
vmInfo.put(port, machine.id());
machine.loadAgent(agentFile.getAbsolutePath(), port + ":" + this.port + ":" + machine.id() + ":" + Logger.isEnabled());
machine.detach();
} catch (final Exception e) {
ExceptionLogger.submitException(e);
}
}
private static final String JAVAFX_SYSTEM_PROPERTIES_KEY = "javafx.version";
private List getRunningJavaFXApplications() {
final List machines = VirtualMachine.list();
Logger.print("Number of running Java applications found: " + machines.size());
final List javaFXMachines = new ArrayList<>();
final Map vmsProperties = new HashMap<>(machines.size());
String currentPid = String.valueOf(ProcessHandle.current().pid());
for (final VirtualMachineDescriptor vmd : machines) {
if (vmd != null && currentPid.equals(vmd.id())) {
continue;
}
try {
final VirtualMachine virtualMachine = VirtualMachine.attach(vmd);
Logger.print("Obtaining properties for Java application with PID:" + virtualMachine.id());
final Properties sysPropertiesMap = virtualMachine.getSystemProperties();
vmsProperties.put(virtualMachine.id(), sysPropertiesMap);
if (sysPropertiesMap != null && sysPropertiesMap.containsKey(JAVAFX_SYSTEM_PROPERTIES_KEY)/* && !sysPropertiesMap.containsKey(SCENIC_VIEW_VM)*/) {
javaFXMachines.add(virtualMachine);
} else {
virtualMachine.detach();
}
// Logger.print("JVM:" + virtualMachine.id() + " detection finished");
} catch (final AttachNotSupportedException | InternalError | IOException ex) {
dumpAttachError(vmd, ex);
}
}
// if (debug && javaFXMachines.isEmpty() && machines.size() > 1) {
// debug("No running JavaFX applications found.");
// for (final Iterator iterator = vmsProperties.keySet().iterator(); iterator.hasNext();) {
// final String id = iterator.next();
//
// final Properties properties = vmsProperties.get(id);
// if (!properties.containsKey(JAVAFX_SYSTEM_PROPERTIES_KEY)) {
// debug("ID:" + id);
// for (@SuppressWarnings("rawtypes") final Iterator iterator2 = properties.keySet().iterator(); iterator2.hasNext();) {
// final String value = (String) iterator2.next();
// debug("\t" + value + "=" + properties.getProperty(value));
// }
// }
// }
// }
return javaFXMachines;
}
private void dumpAttachError(final VirtualMachineDescriptor vmd, final Throwable ex) {
if (!attachError.contains(vmd.id())) {
attachError.add(vmd.id());
System.err.println("Error while obtaining properties for JVM:" + vmd);
ex.printStackTrace();
}
}
private File findAgent() {
File tempf = null;
try {
URL url = RuntimeAttach.class.getResource("/org/fxconnector/remote/RuntimeAttach.class");
if (url.getProtocol().equals("jar")) {
String urlFile = url.getFile();
String fileUrl = urlFile.substring(0, urlFile.indexOf('!'));
tempf = new File(new URL(fileUrl).toURI());
} else if (url.getProtocol().equals("jrt")) {
/**
* Find jar distributed in custom image
*/
tempf = new File(System.getProperty("java.home") + "/lib/scenicview.jar");
}
} catch (MalformedURLException | URISyntaxException e) {
ExceptionLogger.submitException(e, "Attempting to get agent jar.");
}
if (tempf == null || !tempf.exists()) {
/**
* Find jar file in the classpath
*/
final String classPath = System.getProperty("java.class.path");
final String pathSeparator = System.getProperty("path.separator");
if (classPath != null && !classPath.isEmpty()) {
final String[] files = classPath.split(pathSeparator);
for (String file : files) {
if (file.toLowerCase().indexOf("scenicview.jar") != -1) {
tempf = new File(file);
break;
}
}
}
}
if (tempf == null || !tempf.exists()) {
/**
* Find jar file in the modulepath
*/
final String modulePath = System.getProperty("jdk.module.path");
final String pathSeparator = System.getProperty("path.separator");
if (modulePath != null && !modulePath.isEmpty()) {
final String[] files = modulePath.split(pathSeparator);
for (String file : files) {
if (file.toLowerCase().indexOf("scenicview.jar") != -1) {
tempf = new File(file);
break;
}
}
}
}
if (tempf == null || !tempf.exists()) {
// if we are here, lets check the development location, and try to get the jar from there
final File buildLibsDir = new File("build/libs/");
if (buildLibsDir.exists()) {
File[] jarFiles = buildLibsDir.listFiles((f, name) -> name.endsWith(".jar"));
for (File jarFile : jarFiles) {
if (tempf == null || jarFile.length() > tempf.length()) {
tempf = jarFile;
}
}
}
// System.err.println("Cannot load the agent, ScenicView.jar not found here:" + tempf.getAbsolutePath());
}
if (tempf == null) {
Logger.print("Error: Unable to find agent jar file. Exiting Scenic View.");
System.exit(-1);
} else {
Logger.print("Loading agent from: " + tempf.getAbsolutePath());
}
return tempf;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy