All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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