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

org.n52.matlab.control.MatlabConnector Maven / Gradle / Ivy

The newest version!
package org.n52.matlab.control;

/*
 * Copyright (c) 2013, Joshua Kaplan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *  - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *    disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other materials provided with the distribution.
 *  - Neither the name of matlabcontrol nor the names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.n52.matlab.control.MatlabProxy.MatlabThreadCallable;
import org.n52.matlab.control.MatlabProxy.MatlabThreadProxy;
import org.n52.matlab.control.internal.MatlabRMIClassLoaderSpi;

/**
 * This class is used only from inside of the MATLAB JVM. It is responsible for creating instances of
 * {@link JMIWrapperRemote} and sending them to a waiting receiver over RMI.
 * 

* While this class is package private, it can be seen by MATLAB, which does not respect the package privateness of the * class. The public methods in this class can be accessed from inside the MATLAB environment. * * @since 3.0.0 * * @author Joshua Kaplan */ class MatlabConnector { /** * Used to establish connections on a separate thread. */ private static final ExecutorService _connectionExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("MLC Connection Establisher")); /** * The most recently connected receiver retrieved from a Java program running outside of MATLAB. */ private static final AtomicReference _receiverRef = new AtomicReference(); /** * If a connection is currently in progress. */ private static final AtomicBoolean _connectionInProgress = new AtomicBoolean(false); /** * Private constructor so this class cannot be constructed. */ private MatlabConnector() { } private static class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger COUNTER = new AtomicInteger(); private final String _name; NamedThreadFactory(String name) { _name = name; } @Override public Thread newThread(Runnable r) { return new Thread(r, _name + "-" + COUNTER.getAndIncrement()); } } /** * If this session of MATLAB is available to be connected to from an external Java program. It will be available if * it is not currently connected to and there is no connection in progress. * * @return */ static boolean isAvailableForConnection() { boolean available; if(_connectionInProgress.get()) { available = false; } else { RequestReceiver receiver = _receiverRef.get(); boolean connected = false; if(receiver != null) { try { receiver.getReceiverID(); connected = true; } catch(RemoteException e) { } } available = !connected; } return available; } /** * Called from MATLAB at launch. Creates the JMI wrapper and then sends it over RMI to the Java program running in a * separate JVM. * * @param receiverID the key that binds the receiver in the registry * @param port the port the registry is running on * @param initializationTime */ public static void connectFromMatlab(String receiverID, int port) { connect(receiverID, port, false); } /** * Retrieves the receiver and sends over the {@link JMIWrapperRemote} on a separate thread so that MATLAB can * continue to initialize. * * @param receiverID * @param port * @param existingSession */ static void connect(String receiverID, int port, boolean existingSession) { _connectionInProgress.set(true); //Establish the connection on a separate thread to allow MATLAB to continue to initialize //(If this request is coming over RMI then MATLAB has already initialized, but this will not cause an issue.) _connectionExecutor.submit(new EstablishConnectionRunnable(receiverID, port, existingSession)); } /** * A runnable which sets up matlabcontrol inside MATLAB and sends over the remote JMI wrapper. */ private static class EstablishConnectionRunnable implements Runnable { private final String _receiverID; private final int _port; private final boolean _existingSession; /** * The classpath (with each classpath entry as an individual canonical path) of the most recently connected * receiver's JVM. *

* This variable can safely be volatile because the needed behavior is volatile read/write of the array, not * its entries. It is also unlikely the volatile behavior is actually necessary, but it could be if the thread * used by {@link MatlabConnector#_connectionExecutor} died and created a new one - this ensures visibility. */ private static volatile String[] _previousRemoteClassPath = new String[0]; private EstablishConnectionRunnable(String receiverID, int port, boolean existingSession) { _receiverID = receiverID; _port = port; _existingSession = existingSession; } @Override public void run() { //Validate matlabcontrol can be used try { JMIValidator.validateJMIMethods(); } catch(MatlabConnectionException ex) { System.err.println("matlabcontrol is not compatible with this version of MATLAB"); ex.printStackTrace(); return; } //If MATLAB was just launched if(!_existingSession) { //Set the RMI class loader service provider System.setProperty("java.rmi.server.RMIClassLoaderSpi", MatlabRMIClassLoaderSpi.class.getName()); //Make this session of MATLAB of visible over RMI so that reconnections can occur, proceed if it fails try { MatlabBroadcaster.broadcast(_port); } catch(MatlabConnectionException ex) { System.err.println("Reconnecting to this session of MATLAB will not be possible"); ex.printStackTrace(); } } //Send the remote JMI wrapper try { //Get registry Registry registry = LocalHostRMIHelper.getRegistry(_port); //Get the receiver from the registry, if it cannot be retrieved, retry once after waiting. //The retry is attempted because the factory checks periodically to see if the receiver is bound and //will attempt a rebind if it is not. RequestReceiver receiver; try { receiver = (RequestReceiver) registry.lookup(_receiverID); } catch(NotBoundException nbe1) { try { Thread.sleep(RemoteMatlabProxyFactory.RECEIVER_CHECK_PERIOD); try { receiver = (RequestReceiver) registry.lookup(_receiverID); } catch(NotBoundException nbe2) { System.err.println("First attempt to connect to Java application failed"); nbe1.printStackTrace(); System.err.println("Second attempt to connect to Java application failed"); nbe2.printStackTrace(); return; } } catch(InterruptedException ie) { System.err.println("First attempt to connect to Java application failed"); nbe1.printStackTrace(); System.err.println("Interrupted while waiting to retry connection to Java application"); ie.printStackTrace(); return; } } //Hold on the to receiver _receiverRef.set(receiver); //Load a security manager so that remote class loading can occur if(System.getSecurityManager() == null) { System.setSecurityManager(new PermissiveSecurityManager()); } //Tell the RMI class loader of the codebase where the receiver is from, this will allow MATLAB to load //classes defined in the remote JVM, but not in this one MatlabRMIClassLoaderSpi.setCodebase(receiver.getClassPathAsRMICodebase()); //Tell MATLAB's class loader about the codebase where the receiver is from, if not then MATLAB's //environment will freak out when interacting with classes it cannot find the definition of and throw //exceptions with rather confusing messages String[] newClassPath = receiver.getClassPathAsCanonicalPaths(); try { JMIWrapper.invokeAndWait(new ModifyCodebaseCallable(_previousRemoteClassPath, newClassPath)); _previousRemoteClassPath = newClassPath; } catch(MatlabInvocationException e) { System.err.println("Unable to update MATLAB's class loader; issues may arise interacting with " + "classes not defined in MATLAB's Java Virtual Machine"); e.printStackTrace(); } //Create the remote JMI wrapper and then pass it over RMI to the Java application in its own JVM receiver.receiveJMIWrapper(new JMIWrapperRemoteImpl(), _existingSession); } catch(RemoteException ex) { System.err.println("Connection to Java application could not be established"); ex.printStackTrace(); } _connectionInProgress.set(false); } } /** * Modifies MATLAB's dynamic class path. Retrieves the current dynamic class path, removes all of the entries * from the previously connected JVM, adds the new ones, and if the classpath is now different, sets a new dynamic * classpath. */ private static class ModifyCodebaseCallable implements MatlabThreadCallable { private final String[] _toRemove; private final String[] _toAdd; public ModifyCodebaseCallable(String[] oldRemoteClassPath, String[] newRemoteClassPath) { _toRemove = oldRemoteClassPath; _toAdd = newRemoteClassPath; } @Override public Void call(MatlabThreadProxy proxy) throws MatlabInvocationException { //Current dynamic class path String[] curr = (String[]) proxy.returningFeval("javaclasspath", 1, "-dynamic")[0]; //Build new dynamic class path List newDynamic = new ArrayList(); newDynamic.addAll(Arrays.asList(curr)); newDynamic.removeAll(Arrays.asList(_toRemove)); newDynamic.addAll(Arrays.asList(_toAdd)); //If the class path is different, set it if(!newDynamic.equals(Arrays.asList(curr))) { proxy.feval("javaclasspath", new Object[] { newDynamic.toArray(new String[newDynamic.size()]) }); } return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy