/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.clients.fcp; import java.io.IOException; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.EnumMap; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import freenet.clients.fcp.FCPPluginMessage.ClientPermissions; import freenet.node.NodeStarter; import freenet.node.PrioRunnable; import freenet.pluginmanager.FredPluginFCPMessageHandler; import freenet.pluginmanager.FredPluginFCPMessageHandler.ClientSideFCPMessageHandler; import freenet.pluginmanager.FredPluginFCPMessageHandler.PrioritizedMessageHandler; import freenet.pluginmanager.FredPluginFCPMessageHandler.ServerSideFCPMessageHandler; import freenet.pluginmanager.PluginManager; import freenet.pluginmanager.PluginNotFoundException; import freenet.pluginmanager.PluginRespirator; import freenet.support.Executor; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.PooledExecutor; import freenet.support.SimpleFieldSet; import freenet.support.io.NativeThread; /** * <b>Please first read the JavaDoc of the interface {@link FCPPluginConnection} which specifies * this class.</b><br><br> * * ATTENTION:<br> * Objects of this class shall not be handed out directly to server or client applications. * Instead, only hand out a {@link DefaultSendDirectionAdapter} - which can be obtained by * {@link #getDefaultSendDirectionAdapter(SendDirection)}.<br> * This has two reasons:<br> * - The send functions which do not require a {@link SendDirection} will always throw an exception * without an adapter ({@link #send(FCPPluginMessage)} and * {@link #sendSynchronous(FCPPluginMessage, long)}).<br> * - Server plugins must not keep a strong reference to the FCPPluginConnectionImpl * to ensure that the client disconnection mechanism of monitoring garbage collection works, * see {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}. * The adapter prevents servers from keeping a strong reference by internally only keeping a * {@link WeakReference} to the FCPPluginConnectionImpl.<br> * * <h1>Internals</h1><br> * * This section is not interesting to server or client implementations. You might want to read it * if you plan to work on the fred-side implementation of FCP plugin messaging. * * <h2>Code path of sending messages</h2> * <p>There are two possible code paths for client connections, depending upon the location of the * client. The server is always running inside the node.<br><br> * * NOTICE: These two code paths only apply to asynchronous, non-blocking messages. For blocking, * synchronous messages sent by {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)}, * there is an overview at {@link #synchronousSends}. The overview was left out here because they * are built on top of regular messages, so the code paths mentioned here mostly apply.<br><br> * * The two possible paths are:<br/> * <p>1. The server is running in the node, the client is not - also called networked FCP * connections:<br/> * - The client connects to the node via network and sends FCP message of type * <a href="https://wiki.freenetproject.org/FCPv2/FCPPluginMessage">FCPPluginMessage</a> (which * will be internally represented by class {@link FCPPluginClientMessage}).<br/> * - The {@link FCPServer} creates a {@link FCPConnectionHandler} whose * {@link FCPConnectionInputHandler} receives the FCP message.<br/> * - The {@link FCPConnectionInputHandler} uses {@link FCPMessage#create(String, SimpleFieldSet)} * to parse the message and obtain the actual {@link FCPPluginClientMessage}.<br/> * - The {@link FCPPluginClientMessage} uses {@link FCPConnectionHandler#getFCPPluginConnection( * String)} to obtain the FCPPluginConnection to the server.<br/> * - The {@link FCPPluginClientMessage} uses {@link FCPPluginConnection#send(SendDirection, * FCPPluginMessage)} to send the message to the server plugin.<br/> * - The FCP server plugin handles the message at * {@link ServerSideFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection, * FCPPluginMessage)}.<br/> * - As each FCPPluginConnection object exists for the lifetime of a network connection, the FCP * server plugin may store the UUID of the FCPPluginConnection and query it via * {@link PluginRespirator#getPluginConnectionByID(UUID)}. It can use this to send messages to the * client application on its own, that is not triggered by any client messages.<br/> * </p> * <p>2. The server and the client are running in the same node, also called intra-node FCP * connections:</br> * - The client plugin uses {@link PluginRespirator#connectToOtherPlugin(String, * FredPluginFCPMessageHandler.ClientSideFCPMessageHandler)} to try to create a connection.<br/> * - The {@link PluginRespirator} uses {@link FCPServer#createFCPPluginConnectionForIntraNodeFCP( * String, FredPluginFCPMessageHandler.ClientSideFCPMessageHandler)} to get a * FCPPluginConnection.<br> * - The client plugin uses the send functions of the FCPPluginConnection. Those are the same as * with networked FCP connections.<br/> * - The FCP server plugin handles the message at * {@link ServerSideFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection, * FCPPluginMessage)}. That is the same handler as with networked FCP connections.<br/> * - The client plugin keeps a strong reference to the FCPPluginConnection in memory as long as * it wants to keep the connection open.<br/> * - Same as with networked FCP connections, the FCP server plugin can store the UUID of the * FCPPluginConnection and in the future re-obtain the connection by * {@link PluginRespirator#getPluginConnectionByID(UUID)}. It can use this to send messages to the * client application on its own, that is not triggered by any client messages. <br/> * - Once the client plugin is done with the connection, it discards the strong reference to the * FCPPluginConnection. Because the {@link FCPPluginConnectionTracker} monitors garbage * collection of FCPPluginConnection objects, getting rid of all strong references to a * FCPPluginConnection is sufficient as a disconnection mechanism.<br/> * Thus, an intra-node client connection is considered as disconnected once the * FCPPluginConnection is not strongly referenced by the client plugin anymore. If a server * plugin then tries to obtain the client connection by its UUID again (via the aforementioned * {@link PluginRespirator#getPluginConnectionByID(UUID)}, the get will fail. So if the server * plugin stores connection UUIDs, it needs no special disconnection mechanism except for * periodically trying to send a message to each client. Once obtaining the client connection by * its UUID fails, or sending the message fails, the server can opportunistically purge the UUID * from its database. * <br/>This mechanism also works for networked FCP.<br> * </p></p> */ final class FCPPluginConnectionImpl implements FCPPluginConnection { /** Automatically set to true by {@link Logger} if the log level is set to * {@link LogLevel#DEBUG} for this class. * Used as performance optimization to prevent construction of the log strings if it is not * necessary. */ private static transient volatile boolean logDEBUG = false; /** Automatically set to true by {@link Logger} if the log level is set to * {@link LogLevel#MINOR} for this class. * Used as performance optimization to prevent construction of the log strings if it is not * necessary. */ private static transient volatile boolean logMINOR = false; static { // Necessary for automatic setting of logDEBUG and logMINOR Logger.registerClass(FCPPluginConnectionImpl.class); } /** * Unique identifier among all {@link FCPPluginConnection}s. * @see #getID() */ private final UUID id = UUID.randomUUID(); /** * Executor upon which we run threads of the send functions.<br> * Since the send functions can be called very often, it would be inefficient to create a new * {@link Thread} for each one. An {@link Executor} prevents this by having a pool of Threads * which will be recycled. */ private final Executor executor; /** * The class name of the plugin to which this FCPPluginConnectionImpl is connected. */ private final String serverPluginName; /** * The FCP server plugin to which this connection is connected. * * <p>TODO: Optimization / Memory leak fix: Monitor this with a {@link ReferenceQueue} and if it * becomes nulled, remove this FCPPluginConnectionImpl from the map * {@link FCPConnectionHandler#pluginConnectionsByServerName}.<br/> * Currently, it seems not necessary:<br/> * - It can only become null if the server plugin is unloaded / reloaded. Plugin unloading / * reloading requires user interaction or auto update and shouldn't happen frequently.<br/> * - It would only leak one WeakReference per plugin per client network connection. That won't * be much until we have very many network connections. The memory usage of having one thread * per {@link FCPConnectionHandler} to monitor the ReferenceQueue would probably outweigh the * savings.<br/> * - We already opportunistically clean the table at FCPConnectionHandler: If the client * application which is behind the {@link FCPConnectionHandler} tries to send a message using * a FCPPluginConnectionImpl whose server WeakReference is null, it is purged from the said * table at FCPConnectionHandler. So memory will not leak as long as the clients keep trying * to send messages to the nulled server plugin - which they probably will do because they did * already in the past.<br/> * NOTICE: If you do implement this, make sure to not rewrite the ReferenceQueue polling thread * but instead base it upon {@link FCPPluginConnectionTracker}. You should probably * extract a generic class WeakValueMap from that one and use to to power both the * existing class and the one which deals with this variable here.<br> * Also, once you've implemented ReferenceQueue monitoring, remove * {@link #isServerDead()} as it only was added for the opportunistic cleaning due to * lack of a ReferenceQueue and is an ugly function besides that. * </p> * @see #isServerDead() Use isServerDead() to check whether this WeakReference is nulled. */ private final WeakReference<ServerSideFCPMessageHandler> server; /** * For intra-node plugin connections, this is the connecting client. * For networked plugin connections, this is null. */ private final ClientSideFCPMessageHandler client; /** * For networked plugin connections, this is the network connection to which this * FCPPluginConnectionImpl belongs. * For intra-node connections to plugins, this is null. * For each {@link FCPConnectionHandler}, there can only be one FCPPluginConnectionImpl for each * {@link #serverPluginName}. */ private final FCPConnectionHandler clientConnection; /** * @see FCPPluginConnectionImpl#synchronousSends * An overview of how synchronous sends and especially their threading work internally is * provided at the map which stores them. */ private static final class SynchronousSend { /** * {@link FCPPluginConnectionImpl#send(SendDirection, FCPPluginMessage)} shall call * {@link Condition#signal()} upon this once the reply message has been stored to * {@link #reply} to wake up the sleeping {@link FCPPluginConnectionImpl#sendSynchronous( * SendDirection, FCPPluginMessage, long)} thread which is waiting for the reply to arrive. */ private final Condition completionSignal; public FCPPluginMessage reply = null; public SynchronousSend(Condition completionSignal) { this.completionSignal = completionSignal; } } /** * For each message sent with the <i>blocking</i> send function * {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)} this contains a * {@link SynchronousSend} object which shall be used to signal the completion of the * synchronous send to the blocking sendSynchronous() thread. Signaling the completion tells the * blocking sendSynchronous() function that the remote side has sent a reply message to * acknowledge that the original message was processed and sendSynchronous() may return now. * In addition, the reply is added to the SynchronousSend object so that sendSynchronous() can * return it to the caller.<br><br> * * The key is the identifier {@link FCPPluginMessage#identifier} of the original message which * was sent by sendSynchronous().<br><br> * * An entry shall be added by sendSynchronous() when a new synchronous send is started, and then * it shall wait for the Condition {@link SynchronousSend#completionSignal} to be signaled.<br> * When the reply message is received, the node will always dispatch it via * {@link #send(SendDirection, FCPPluginMessage)}. Thus, that function is obliged to check this * map for whether there is an entry for each received reply. If it contains a SynchronousSend * for the identifier of a given reply, send() shall store the reply message in it, and then * call {@link Condition#signal()} upon the SynchronousSend's Condition to cause the blocking * sendSynchronous() function to return.<br> * The sendSynchronous() shall take the job of removing the entry from this map.<br><br> * * Thread safety is to be guaranteed by the {@link #synchronousSendsLock}.<br><br> * * When implementing the mechanisms which use this map, please be aware of the fact that bogus * remote implementations could:<br> * - Not sent a reply message at all, even though they should. This shall be compensated by * sendSynchronous() always specifying a timeout when waiting upon the Conditions.<br> * - Send <i>multiple</i> reply messages for the same identifier even though they should only * send one. This probably won't matter though:<br> * * The first arriving reply will complete the matching sendSynchronous() call.<br> * * Any subsequent replies will not find a matching entry in this table, which is the * same situation as if the reply was to a <i>non</i>-synchronous send. Non-synchronous sends * are a normal thing, and thus handling their replies is implemented. It will cause the * reply to be shipped to the message handler interface of the server/client instead of * being returned by sendSynchronous() though, which could confuse it. But in that case * it will probably just log an error message and continue working as normal. * <br><br> * * TODO: Optimization: We do not need the order of the map, and thus this could be a HashMap * instead of a TreeMap. We do not use a HashMap for scalability: Java HashMaps never shrink, * they only grow. As we cannot predict how much parallel synchronous sends server/client * implementations will run, we do need a shrinking map. So we use TreeMap. We should use * an automatically shrinking HashMap instead once we have one. This is also documented * <a href="https://bugs.freenetproject.org/view.php?id=6320">in the bugtracker</a>. */ private final TreeMap<String, SynchronousSend> synchronousSends = new TreeMap<String, SynchronousSend>(); /** * Shall be used to ensure thread-safety of {@link #synchronousSends}. <br> * (Please read its JavaDoc before continuing to read this JavaDoc: It explains the mechanism * of synchronous sends, and it is assumed that you understand it in what follows here.)<br><br> * * It is a {@link ReadWriteLock} because synchronous sends shall by design be used infrequently, * and thus there will be more reads checking for an existing synchronous send than writes * to terminate one. * (It is a {@link ReentrantReadWriteLock} because that is currently the only implementation of * ReadWriteLock, the re-entrancy is probably not needed by the actual code.) */ private final ReadWriteLock synchronousSendsLock = new ReentrantReadWriteLock(); /** * A {@link DefaultSendDirectionAdapter} is an adapter which encapsulates a * FCPPluginConnectionImpl object with a default {@link SendDirection} to implement the send * functions which don't require a direction parameter:<br> * - {@link FCPPluginConnection#send(FCPPluginMessage)}<br> * - {@link FCPPluginConnection#sendSynchronous(FCPPluginMessage, long)}<br><br> * * For each possible {@link SendDirection}, this map keeps the responsible adapter. */ private final EnumMap<SendDirection, DefaultSendDirectionAdapter> defaultSendDirectionAdapters = new EnumMap<SendDirection, DefaultSendDirectionAdapter>(SendDirection.class); /** * For being used by networked FCP connections:<br/> * The server is running within the node, and its message handler is accessible as an * implementor of {@link ServerSideFCPMessageHandler}.<br/> * The client is not running within the node, it is attached by network with a * {@link FCPConnectionHandler}.<br/> * * @see #constructForNetworkedFCP(FCPPluginConnectionTracker, Executor, PluginManager, String, * FCPConnectionHandler) * The public interface to this constructor. */ private FCPPluginConnectionImpl(FCPPluginConnectionTracker tracker, Executor executor, String serverPluginName, ServerSideFCPMessageHandler serverPlugin, FCPConnectionHandler clientConnection) { assert(tracker != null); assert(executor != null); assert(serverPlugin != null); assert(serverPluginName != null); assert(clientConnection != null); this.executor = executor; this.serverPluginName = serverPluginName; this.server = new WeakReference<ServerSideFCPMessageHandler>(serverPlugin); this.client = null; this.clientConnection = clientConnection; this.defaultSendDirectionAdapters.put(SendDirection.ToServer, new SendToServerAdapter(this)); // new SendToClientAdapter() will need to query this connection from the tracker already. // Thus, we have to register before constructing it. tracker.registerConnection(this); this.defaultSendDirectionAdapters.put(SendDirection.ToClient, new SendToClientAdapter(tracker, id)); } /** * For being used by networked FCP connections:<br/> * The server is running within the node, and its message handler will be queried from the * {@link PluginManager} via the given String serverPluginName.<br/> * The client is not running within the node, it is attached by network with the given * {@link FCPConnectionHandler} clientConnection.<br><br> * * The returned connection is registered at the given {@link FCPPluginConnectionTracker}. * <br><br> * * ATTENTION:<br> * Objects of this class shall not be handed out directly to server or client applications. * Instead, only hand out a {@link DefaultSendDirectionAdapter} - which can be obtained by * {@link #getDefaultSendDirectionAdapter(SendDirection)}.<br> * This has two reasons:<br> * - The send functions which do not require a {@link SendDirection} will always throw an * exception without an adapter ({@link #send(FCPPluginMessage)} and * {@link #sendSynchronous(FCPPluginMessage, long)}).<br> * - Server plugins must not keep a strong reference to the FCPPluginConnectionImpl * to ensure that the client disconnection mechanism of monitoring garbage collection works, * see {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}. * The adapter prevents servers from keeping a strong reference by internally only keeping a * {@link WeakReference} to the FCPPluginConnectionImpl.<br> */ static FCPPluginConnectionImpl constructForNetworkedFCP(FCPPluginConnectionTracker tracker, Executor executor, PluginManager serverPluginManager, String serverPluginName, FCPConnectionHandler clientConnection) throws PluginNotFoundException { assert(tracker != null); assert(executor != null); assert(serverPluginManager != null); assert(serverPluginName != null); assert(clientConnection != null); return new FCPPluginConnectionImpl(tracker, executor, serverPluginName, serverPluginManager.getPluginFCPServer(serverPluginName), clientConnection); } /** * For being used by intra-node connections between two plugins:<br/> * Both the server and the client are running within the same node, so objects of their FCP * message handling interfaces are available:<br/> * The server's message handler is accessible as an implementor of * {@link ServerSideFCPMessageHandler}.<br> * The client's message handler is accessible as an implementor of * {@link ClientSideFCPMessageHandler}.<br> * * @see #constructForIntraNodeFCP(FCPPluginConnectionTracker, Executor, PluginManager, String, * ClientSideFCPMessageHandler) * The public interface to this constructor. */ private FCPPluginConnectionImpl(FCPPluginConnectionTracker tracker, Executor executor, String serverPluginName, ServerSideFCPMessageHandler server, ClientSideFCPMessageHandler client) { assert(tracker != null); assert(executor != null); assert(serverPluginName != null); assert(server != null); assert(client != null); this.executor = executor; this.serverPluginName = serverPluginName; this.server = new WeakReference<ServerSideFCPMessageHandler>(server); this.client = client; this.clientConnection = null; this.defaultSendDirectionAdapters.put(SendDirection.ToServer, new SendToServerAdapter(this)); // new SendToClientAdapter() will need to query this connection from the tracker already. // Thus, we have to register before constructing it. tracker.registerConnection(this); this.defaultSendDirectionAdapters.put(SendDirection.ToClient, new SendToClientAdapter(tracker, id)); } /** * For being used by intra-node connections between two plugins:<br/> * Both the server and the client are running within the same node, so their FCP interfaces are * available:<br/> * The server plugin will be queried from given {@link PluginManager} via the given String * serverPluginName.<br> * The client message handler is available as the passed {@link ClientSideFCPMessageHandler} * client.<br><br> * * The returned connection is registered at the given {@link FCPPluginConnectionTracker}. * <br><br> * * ATTENTION:<br> * Objects of this class shall not be handed out directly to server or client applications. * Instead, only hand out a {@link DefaultSendDirectionAdapter} - which can be obtained by * {@link #getDefaultSendDirectionAdapter(SendDirection)}.<br> * This has two reasons:<br> * - The send functions which do not require a {@link SendDirection} will always throw an * exception without an adapter ({@link #send(FCPPluginMessage)} and * {@link #sendSynchronous(FCPPluginMessage, long)}).<br> * - Server plugins must not keep a strong reference to the FCPPluginConnectionImpl * to ensure that the client disconnection mechanism of monitoring garbage collection works, * see {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}. * The adapter prevents servers from keeping a strong reference by internally only keeping a * {@link WeakReference} to the FCPPluginConnectionImpl.<br> */ static FCPPluginConnectionImpl constructForIntraNodeFCP(FCPPluginConnectionTracker tracker, Executor executor, PluginManager serverPluginManager, String serverPluginName, ClientSideFCPMessageHandler client) throws PluginNotFoundException { assert(executor != null); assert(serverPluginManager != null); assert(serverPluginName != null); assert(client != null); return new FCPPluginConnectionImpl(tracker, executor, serverPluginName, serverPluginManager.getPluginFCPServer(serverPluginName), client); } /** * ONLY for being used in unit tests.<br> * This is similar to intra-node connections in regular operation: Both the server and client * are running in the same VM. You must implement both the server and client side message * handler in the unit test and pass them to this constructor.<br><br> * * Notice: Some server plugins might use {@link PluginRespirator#getPluginConnectionByID(UUID)} * to obtain FCPPluginConnectionImpl objects. They likely won't work with connections created by * this because it doesn't create a PluginRespirator. To get a {@link PluginRespirator} * available in unit tests, you might want to use * {@link NodeStarter#createTestNode(freenet.node.NodeStarter.TestNodeParameters)} instead * of this constructor:<br> * - The test node can be used to load the plugin as a JAR.<br> * - As loading a plugin by JAR is the same mode of operation as with a regular node, * there will be a PluginRespirator available to it.<br> * - {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)} can then * be used for obtaining a FCPPluginConnection instead of this constructor. This also is the * function to obtain a FCPPluginConnection which is used in regular mode of operation.<br> * - The aforementioned {@link PluginRespirator#getPluginConnectionByID(UUID)} will then work * for FCPPluginConnections obtained through the connectToOtherPlugin(). */ public static FCPPluginConnectionImpl constructForUnitTest(ServerSideFCPMessageHandler server, ClientSideFCPMessageHandler client) { assert(server != null); assert(client != null); FCPPluginConnectionTracker tracker = new FCPPluginConnectionTracker(); tracker.start(); return new FCPPluginConnectionImpl( tracker, new PooledExecutor(), server.toString(), server, client); } @Override public UUID getID() { return id; } /** * ATTENTION: Only for internal use in {@link FCPConnectionHandler#getFCPPluginConnection( * String)}.<br> * Server / client code should instead always send messages, for example via * {@link #send(SendDirection, FCPPluginMessage)}, to check whether the connection is alive. * This is to ensure that the implementation of this class could safely be changed to allow the * server to be attached by network instead of always running locally in the same node as it * currently is. Also see below.<br> * * @return * <p>True if the server plugin has been unloaded. Once this returns true, this * FCPPluginConnectionImpl <b>cannot</b> be repaired, even if the server plugin is loaded * again. Then you should discard this connection and create a fresh one.</p> * * <p><b>ATTENTION:</b> Future implementations of {@link FCPPluginConnection} might allow * the server plugin to reside in a different node, and only be attached by network. Due to * the unreliability of network connections, then this function will not be able to reliably * detect whether the server is dead.<br> * To prepare for that, you <b>must not</b> assume that the connection to the server is * still fine just because this returns false = server is alive. Consider false / server is * alive merely an indication, true / server is dead as the definite truth.<br> * If you need to validate a connection to be alive, send periodic pings. </p> */ boolean isServerDead() { return server.get() == null; } /** * @return * The permission level of the client, depending on things such as its IP address.<br> * For intra-node connections, it is {@link ClientPermissions#ACCESS_DIRECT}.<br><br> * * <b>ATTENTION:</b> The return value can change at any point in time, so you should check * this before deploying each FCP message.<br> * This is because the user is free to reconfigure IP-address restrictions on the node's web * interface whenever he wants to. */ private ClientPermissions getCurrentClientPermissions() { if(clientConnection != null) { // Networked FCP return clientConnection.hasFullAccess() ? ClientPermissions.ACCESS_FCP_FULL : ClientPermissions.ACCESS_FCP_RESTRICTED; } else { // Intra-node FCP assert(client != null); return ClientPermissions.ACCESS_DIRECT; } } @Override public void send(final SendDirection direction, FCPPluginMessage message) throws IOException { // We first have to compute the message.permissions field ourselves - we shall ignore what // caller said for security. ClientPermissions currentClientPermissions = (direction == SendDirection.ToClient) ? null // Server-to-client messages do not have permissions. : getCurrentClientPermissions(); // We set the permissions by creating a fresh FCPPluginMessage object so the caller cannot // overwrite what we compute. message = FCPPluginMessage.constructRawMessage(currentClientPermissions, message.identifier, message.params, message.data, message.success, message.errorCode, message.errorMessage); // Now that the message is completely initialized, we can dump it to the logfile. if(logDEBUG) { Logger.debug(this, "send(): direction = " + direction + "; " + "message = " + message); } // True if the target server or client message handler is running in this VM. // This means that we can call its message handling function in a thread instead of // sending a message over the network. // Notice that we do not check for server != null because that is not allowed by this class. final boolean messageHandlerExistsLocally = (direction == SendDirection.ToServer) || (direction == SendDirection.ToClient && client != null); if(!messageHandlerExistsLocally) { dispatchMessageByNetwork(direction, message); return; } assert(direction == SendDirection.ToServer ? server != null : client != null) : "We already decided that the message handler exists locally. " + "We should have only decided so if the handler is not null."; // The message handler is determined to be local at this point. There are two possible // types of local message handlers: // 1) An object provided by the server/client which implements FredPluginFCPMessageHandler. // The message is delivered by executing a callback on that object, with the message // as parameter. // 2) A call to sendSynchronous() which is blocking because it is waiting for a reply // to the message it sent so it can return the reply message to the caller. // The reply message is delivered by passing it to the sendSynchronous() thread through // an internal table of this class. // // The following function call checks for whether case 2 applies, and handles it if yes: // If there is such a waiting sendSynchronous() thread, it delivers the message to it, and // returns true, and we are done: By contract, messages are preferably delivered to // sendSynchronous(). // If there was no sendSynchronous() thread, it returns false, and we must continue to // handle case 1. if(dispatchMessageLocallyToSendSynchronousThreadIfExisting(direction, message)) return; // We now know that the message handler is not attached by network, and that it is not a // sendSynchronous() thread. So the only thing it can be is a FredPluginFCPMessageHandler, // and we now determine whether it is the one of the client or the server. final FredPluginFCPMessageHandler messageHandler = (direction == SendDirection.ToServer) ? server.get() : client; if(messageHandler == null) { // server is a WeakReference which can be nulled if the server plugin was unloaded. // client is not a WeakReference, we already checked for it to be non-null. // Thus, in this case here, the server plugin has been unloaded so we can have // an error message which specifically talks about the *server* plugin. throw new IOException("The server plugin has been unloaded."); } // We now have the right FredPluginFCPMessageHandler, it is still alive, and so we can can // pass the message to it. dispatchMessageLocallyToMessageHandler(messageHandler, direction, message); } /** * Backend for {@link #send(SendDirection, FCPPluginMessage)} to dispatch messages which need * to be transported by network.<br><br> * * This shall only be called for messages for which it was determined that the message handler * is not a plugin running in the local VM. */ private void dispatchMessageByNetwork(final SendDirection direction, final FCPPluginMessage message) throws IOException { // The message handler is attached by network. // In theory, we could construct a mock FredPluginFCPMessagehandler object for it to // pretend it was a local message. But then we wouldn't know the reply message immediately // because the messages take time to travel over the network. This wouldn't work with the // local message dispatching code as it needs to know the reply immediately so it can send // it out. To get the reply, we would have to create a thread which would exist until the // reply arrives over the network. // So instead, for simplicity and reduced thread count, we just queue the message directly // to the network queue here and return. assert (direction == SendDirection.ToClient) : "By design, this class always shall execute in the same VM as the server plugin. " + "So for networked messages, we should always be sending to the client."; assert (clientConnection != null) : "Trying to send a message over the network to the client. " + "So the network connection to it should not be null."; if (clientConnection.isClosed()) throw new IOException("Connection to client closed for " + this); clientConnection.send(new FCPPluginServerMessage(serverPluginName, message)); } /** * Backend for {@link #send(SendDirection, FCPPluginMessage)} to dispatch messages to a thread * waiting in {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)} for the message. * <br><br> * * This shall only be called for messages for which it was determined that the message handler * is a plugin running in the local VM. * * @return * True if there was a thread waiting for the message and the message was dispatched to it. * You <b>must not</b> dispatch it to the {@link FredPluginFCPMessageHandler} then.<br><br> * * False if there was no thread waiting for the message. You <b>must<b/> dispatch it to the * {@link FredPluginFCPMessageHandler} then.<br><br> * * (Both these rules are specified in the documentation of sendSynchronous().) * @see FCPPluginConnectionImpl#synchronousSends * An overview of how synchronous sends and especially their threading work internally is * provided at the map which stores them. */ private boolean dispatchMessageLocallyToSendSynchronousThreadIfExisting( final SendDirection direction, final FCPPluginMessage message) { // Since the message handler is determined to be local at this point, we now must check // whether it is a blocking sendSynchronous() thread instead of a regular // FredPluginFCPMessageHandler. // sendSynchronous() does the following: It sends a message and then blocks its thread // waiting for a message replying to it to arrive so it can return it to the caller. // If the message we are processing here is a reply, it might be the one which a // sendSynchronous() is waiting for. // So it is our job to pass the reply to a possibly existing sendSynchronous() thread. // We do this through the Map FCPPluginConnectionImpl.synchronousSends, which is guarded by // FCPPluginConnectionImpl.synchronousSendsLock. Also see the JavaDoc of the Map for an // overview of this mechanism. if(!message.isReplyMessage()) { return false; } // Since the JavaDoc of sendSynchronous() tells people to use it not very often due to // the impact upon thread count, we assume that the percentage of messages which pass // through here for which there is an actual sendSynchronous() thread waiting is small. // Thus, a ReadWriteLock is used, and we here only take the ReadLock, which can be taken // by *multiple* threads at once. We then read the map to check whether there is a // waiter, and if there is, take the write lock to hand the message to it. // (The implementation of ReentrantReadWritelock does not allow upgrading a readLock() // to a writeLock(), so we must release it in between and re-check afterwards.) // TODO: Performance: If this turns out to be a bottleneck, add a // "Synchronous={True, False}" flag to messages so we only have to check the table if // Synchronous=True, and can return false immediately otherwise. (If Synchronous=True, we // still will have to check the table whether a waiter is existing because it might have // timed out already) synchronousSendsLock.readLock().lock(); try { if(!synchronousSends.containsKey(message.identifier)) { return false; } } finally { synchronousSendsLock.readLock().unlock(); } synchronousSendsLock.writeLock().lock(); try { SynchronousSend synchronousSend = synchronousSends.get(message.identifier); if(synchronousSend == null) { // The waiting sendSynchronous() has probably returned already because its // timeout expired. // So by returning false, we ask the caller to deliver the message to the // regular message handling interface to make sure that it is not lost. return false; } assert(synchronousSend.reply == null) : "One identifier should not be used for multiple messages or replies"; synchronousSend.reply = message; // Wake up the waiting synchronousSend() thread synchronousSend.completionSignal.signal(); return true; } finally { synchronousSendsLock.writeLock().unlock(); } } /** * Backend for {@link #send(SendDirection, FCPPluginMessage)} to dispatch messages to a * {@link FredPluginFCPMessageHandler}.<br><br> * * This shall only be called for messages for which it was determined that the message handler * is a plugin running in the local VM.<br><br> * * The message will be dispatched in a separate thread so this function can return quickly. */ private void dispatchMessageLocallyToMessageHandler( final FredPluginFCPMessageHandler messageHandler, final SendDirection direction, final FCPPluginMessage message) { final Runnable messageDispatcher = new PrioRunnable() { @Override public void run() { FCPPluginMessage reply = null; try { try { reply = messageHandler.handlePluginFCPMessage( getDefaultSendDirectionAdapter(direction.invert()), message); } catch(Error e) { // TODO: Code quality: This is a workaround for Java 6 not having // "catch(RuntimeException | Error e)". Once we are on Java 7, remove this // catch() block, and catch both types with the catch() block below. throw new RuntimeException(e); } } catch(RuntimeException e) { // The message handler is a server or client implementation, and thus as third // party code might have bugs. So we need to catch any undeclared throwables. // Notice that this is not normal mode of operation: Instead of throwing, // the JavaDoc requests message handlers to return a reply with success=false. String errorMessage = "FredPluginFCPMessageHandler threw." + " See JavaDoc of its member interfaces for how signal errors properly." + " connection = " + FCPPluginConnectionImpl.this + "; SendDirection = " + direction + "; message = " + message; Logger.error(messageHandler, errorMessage, e); if(!message.isReplyMessage()) { // If the original message was not a reply already, we are allowed to send a // reply with success=false to indicate the error to the remote side. // This allows possibly existing, waiting sendSynchronous() calls to fail // quickly instead of having to wait for the timeout because no reply // arrives. reply = FCPPluginMessage.constructReplyMessage(message, null, null, false, "InternalError", errorMessage + "; Throwable = " + e.toString()); } } if(reply != null) { // TODO: Performance: The below checks might be converted to assert() or // be prefixed with if(logMINOR). // Not doing this now since the FredPluginFCPMessageHandler API which specifies // those requirements is new and thus quite a few client applications might be // converted to it soon, and do those beginners mistakes. // After everyone has gotten used to it, we can move to the more lax checking. // An alternate solution would be to not use the FCPPluginMessage object // which was returned by the message handler but always re-construct it to // follow the standards. // Replying to replies is disallowed to prevent infinite bouncing. if(message.isReplyMessage()) { Logger.error(messageHandler, "FredPluginFCPMessageHandler tried to send a" + " reply to a reply. Discarding it. See JavaDoc of its member" + " interfaces for how to do this properly." + " connection = " + FCPPluginConnectionImpl.this + "; original message SendDirection = " + direction + "; original message = " + message + "; reply = " + reply); reply = null; } else if(!reply.isReplyMessage()) { Logger.error(messageHandler, "FredPluginFCPMessageHandler tried to send a" + " non-reply message as reply. See JavaDoc of its member interfaces" + " for how to do this properly." + " connection = " + FCPPluginConnectionImpl.this + "; original message SendDirection = " + direction + "; original message = " + message + "; reply = " + reply); reply = null; } else if(!reply.identifier.equals(message.identifier)) { Logger.error(messageHandler, "FredPluginFCPMessageHandler tried to send a" + " reply with with different identifier than original message." + " See JavaDoc of its member interfaces for how to do this properly." + " connection = " + FCPPluginConnectionImpl.this + "; original message SendDirection = " + direction + "; original message = " + message + "; reply = " + reply); reply = null; } } else if(reply == null) { if(!message.isReplyMessage()) { // The message handler did not not ship a reply even though it would have // been allowed to because the original message was not a reply. // This shouldn't be done: Not sending a success reply at least will cause // sendSynchronous() threads to keep waiting for the reply until timeout. Logger.warning( messageHandler, "Fred did not receive a reply from the message " + "handler even though it was allowed to reply. " + "This would cause sendSynchronous() to timeout! " + " connection = " + FCPPluginConnectionImpl.this + "; SendDirection = " + direction + "; message = " + message); } } // We already tried to set a reply if one is needed. If it is still null now, then // we do not have to send one for sure, so we can return. if(reply == null) { return; } try { send(direction.invert(), reply); } catch (IOException e) { // The remote partner has disconnected, which can happen during normal // operation. // There is nothing we can do to get the IOException out to the caller of the // initial send() of the original message which triggered the reply sending. // - We are in a different thread, the initial send() has returned already. // So we just log it, because it still might indicate problems if we try to // send after disconnection. // We log it marked as from the messageHandler instead of the // FCPPluginConnectionImpl: // The messageHandler will be an object of the server or client plugin, // from a class contained in it. So there is a chance that the developer // has logging enabled for that class, and thus we log it marked as from that. Logger.warning(messageHandler, "Sending reply from FredPluginFCPMessageHandler" + " failed, the connection was closed already." + " connection = " + FCPPluginConnectionImpl.this + "; original message SendDirection = " + direction + "; original message = " + message + "; reply = " + reply, e); } } @Override public int getPriority() { NativeThread.PriorityLevel priority = NativeThread.PriorityLevel.NORM_PRIORITY; if(messageHandler instanceof PrioritizedMessageHandler) { try { priority = ((PrioritizedMessageHandler)messageHandler).getPriority(message); } catch(Throwable t) { Logger.error(messageHandler, "Message handler's getPriority() threw!", t); } } return priority.value; } /** @return A suitable {@link String} for use as the name of this thread */ @Override public String toString() { // Don't use FCPPluginConnection.toString() as it would be too long to fit in // the thread list on the Freenet FProxy web interface. return "FCPPluginConnection for " + serverPluginName; } }; executor.execute(messageDispatcher, messageDispatcher.toString()); } @Override public FCPPluginMessage sendSynchronous(SendDirection direction, FCPPluginMessage message, long timeoutNanoSeconds) throws IOException, InterruptedException { if(message.isReplyMessage()) { throw new IllegalArgumentException("sendSynchronous() cannot send reply messages: " + "If it did send a reply message, it would not get another reply back. " + "But a reply is needed for sendSynchronous() to determine when to return."); } assert(timeoutNanoSeconds > 0) : "Timeout should not be negative"; assert(timeoutNanoSeconds <= TimeUnit.MINUTES.toNanos(1)) : "Please use sane timeouts to prevent thread congestion"; synchronousSendsLock.writeLock().lock(); try { final Condition completionSignal = synchronousSendsLock.writeLock().newCondition(); final SynchronousSend synchronousSend = new SynchronousSend(completionSignal); // An assert() instead of a throwing is fine: // - The constructor of FCPPluginMessage which we tell the user to use in the JavaDoc // does generate a random identifier, so collisions will only happen if the user // ignores the JavaDoc or changes the constructor. // - If the assert is not true, then the following put() will replace the old // SynchronousSend, so its Condition will never get signaled, and its // thread waiting in sendSynchronous() will timeout safely. It IS possible that this // thread will then get a reply which does not belong to it. But the wrong reply will // only affect the caller, the FCPPluginConnectionImpl will keep working fine, // especially no threads will become stalled for ever. As the caller is at fault for // the issue, it is fine if he breaks his own stuff :) The JavaDoc also documents this assert(!synchronousSends.containsKey(message.identifier)) : "FCPPluginMessage.identifier should be unique"; synchronousSends.put(message.identifier, synchronousSend); if(logMINOR) { Logger.minor(this, "sendSynchronous(): Started for identifier " + message.identifier + "; synchronousSends table size: " + synchronousSends.size()); } send(direction, message); // Message is sent, now we wait for the reply message to be put into the SynchronousSend // object by the thread which receives the reply message. // - That usually happens at FCPPluginConnectionImpl.send(). // Once it has put it into the SynchronousSend object, it will call signal() upon // our Condition completionSignal. // This will make the following awaitNanos() wake up and return true, which causes this // function to be able to return the reply. do { // The compleditionSignal is a Condition which was created from the // synchronousSendsLock.writeLock(), so it will be released by the awaitNanos() // while it is blocking, and re-acquired when it returns. timeoutNanoSeconds = completionSignal.awaitNanos(timeoutNanoSeconds); if(timeoutNanoSeconds <= 0) { // Include the FCPPluginMessage in the Exception so the developer can determine // whether it is an issue of the remote side taking a long time to execute // for certain messages. throw new IOException("sendSynchronous() timed out waiting for reply! " + " connection = " + FCPPluginConnectionImpl.this + "; SendDirection = " + direction + "; message = " + message); } // The thread which sets synchronousSend.reply to be non-null calls // completionSignal.signal() only after synchronousSend.reply has been set. // So the naive assumption would be that at this point of code, // synchronousSend.reply would be non-null because awaitNanos() should only return // true after signal() was called. // However, Condition.awaitNanos() can wake up "spuriously", i.e. wake up without // actually having been signal()ed. See the JavaDoc of Condition. // So after awaitNanos() has returned true to indicate that it might have been // signaled we still need to check whether the semantic condition which would // trigger signaling is *really* met, which we do with this if: if(synchronousSend.reply != null) { assert(synchronousSend.reply.identifier.equals(message.identifier)); return synchronousSend.reply; } // The spurious wakeup described at the above if() has happened, so we loop. } while(true); } finally { // We MUST always remove the SynchronousSend object which we added to the map, // otherwise it will leak memory eternally. synchronousSends.remove(message.identifier); if(logMINOR) { Logger.minor(this, "sendSynchronous(): Done for identifier " + message.identifier + "; synchronousSends table size: " + synchronousSends.size()); } synchronousSendsLock.writeLock().unlock(); } } /** * Encapsulates a FCPPluginConnectionImpl object and a default {@link SendDirection} to * implement the send functions which don't require a direction parameter:<br> * - {@link FCPPluginConnection#send(FCPPluginMessage)}<br> * - {@link FCPPluginConnection#sendSynchronous(FCPPluginMessage, long)}<br><br> * * An adapter is needed instead of storing this as a member variable in FCPPluginConnectionImpl * because a single FCPPluginConnectionImpl object is used by both to the the server AND the * client which it connects, and their default send direction will be different:<br> * A server will want to send to the client by default, but the client will want to default to * sending to the server.<br><br> * * Is abstract and has two implementing child classes (to implement differing internal * requirements, see {@link #getConnection()}):<br> * - {@link SendToClientAdapter} for default direction {@link SendDirection#ToClient}.<br> * - {@link SendToServerAdapter} for default direction {@link SendDirection#ToServer}.<br><br> * * NOTICE: Server plugins must not keep a strong reference to the FCPPluginConnectionImpl * to ensure that the client disconnection mechanism of monitoring garbage collection works. * This class also serves the purpose of preventing servers from keeping a strong reference:<br> * Uses of class FCPPluginConnectionImpl are told by the documentation to never hand out a * FCPPluginConnectionImpl itself to servers, but only give them adapters. Since the * {@link SendToClientAdapter} only keeps a {@link WeakReference} to the * FCPPluginConnectionImpl, by only handing out the adapter, servers are prevented from keeping * a strong reference to the FCPPluginConnectionImpl. */ private abstract static class DefaultSendDirectionAdapter implements FCPPluginConnection { private final SendDirection defaultDirection; DefaultSendDirectionAdapter(SendDirection defaultDirection) { this.defaultDirection = defaultDirection; } /** * Returns the encapsulated backend FCPPluginConnection which shall be used for sending.<br> * <br> * * Abstract because storage of a FCPPluginConnection object is different for servers and * clients and thus must be implemented in separate child classes:<br> * - Clients may and must store a FCPPluginConnection with a hard reference because a * connection is considered as closed once there is no more hard reference to it.<br> * Disconnection is detected by monitoring the FCPluginConnection for garbage collection. * <br> * - Servers must store a FCPPluginConnection with a {@link WeakReference} (or always query * it by UUID from the node) to ensure that they will get garbage connected once the * client decides to disconnect by dropping all strong references. */ protected abstract FCPPluginConnection getConnection() throws IOException; @Override public void send(FCPPluginMessage message) throws IOException { send(defaultDirection, message); } @Override public FCPPluginMessage sendSynchronous(FCPPluginMessage message, long timeoutNanoSeconds) throws IOException, InterruptedException { return sendSynchronous(defaultDirection, message, timeoutNanoSeconds); } @Override public void send(SendDirection direction, FCPPluginMessage message) throws IOException { getConnection().send(direction, message); } @Override public FCPPluginMessage sendSynchronous(SendDirection direction, FCPPluginMessage message, long timeoutNanoSeconds) throws IOException, InterruptedException { return getConnection().sendSynchronous(direction, message, timeoutNanoSeconds); } } /** * Encapsulates a FCPPluginConnectionImpl object with a default {@link SendDirection} of * {@link SendDirection#ToClient} to implement the send functions which don't require a * direction parameter:<br> * - {@link FCPPluginConnection#send(FCPPluginMessage)}<br> * - {@link FCPPluginConnection#sendSynchronous(FCPPluginMessage, long)}<br><br> * * ATTENTION: Must only be used by the server, not by the client: Clients must keep a strong * reference to the connection to prevent its garbage collection (= disconnection), but this * does not keep a strong reference.<br> * See section "Disconnecting properly" at {@link PluginRespirator#connectToOtherPlugin( * String, ClientSideFCPMessageHandler)}.<br><br> * * NOTICE: Server plugins must not keep a strong reference to the FCPPluginConnectionImpl * to ensure that the client disconnection mechanism of monitoring garbage collection works. * This class also serves the purpose of preventing servers from keeping a strong reference:<br> * Uses of class FCPPluginConnectionImpl are told by the documentation to never hand out a * FCPPluginConnectionImpl itself to servers, but only give them adapters. Since the * SendToClientAdapter only keeps a {@link WeakReference} to the FCPPluginConnectionImpl, by * only handing out the adapter, servers are prevented from keeping a strong reference to the * FCPPluginConnectionImpl.<br> * As a consequence, please do never change this class to keep a strong reference to the * FCPPluginConnectionImpl. */ private static final class SendToClientAdapter extends DefaultSendDirectionAdapter { /** * {@link WeakReference} to the underlying FCPPluginConnectionImpl.<br> * Once this becomes null, the connection is definitely dead - see * {@link FCPPluginConnectionTracker}.<br> * Notice: The ConnectionWeakReference child class of {@link WeakReference} is used because * it also stores the connection ID, which is needed for {@link #getID()}. */ private final FCPPluginConnectionTracker.ConnectionWeakReference connectionRef; /** * For CPU performance of not calling * {@link FCPPluginConnectionTracker#getConnectionWeakReference(UUID)} for every * {@link #send(FCPPluginMessage)}, please use * {@link FCPPluginConnectionImpl#getDefaultSendDirectionAdapter(SendDirection)} * whenever possible to reuse adapters instead of creating new ones with this constructor.*/ SendToClientAdapter(FCPPluginConnectionTracker tracker, UUID connectionID) { super(SendDirection.ToClient); // Reuse the WeakReference from the FCPPluginConnectionTracker instead of creating our // own one since it has to keep a WeakReference for every connection anyway, and // WeakReferences might be expensive to maintain for the VM. try { this.connectionRef = tracker.getConnectionWeakReference(connectionID); } catch (IOException e) { // This function should only be used during construction of the underlying // FCPPluginConnectionImpl. While it is being constructed, it should not be // considered as disconnected already, and thus the FCPPluginConnectionTracker // should never throw IOException. throw new RuntimeException("SHOULD NOT HAPPEN: ", e); } } @Override protected FCPPluginConnection getConnection() throws IOException { FCPPluginConnection connection = connectionRef.get(); if(connection == null) { throw new IOException("Client has closed the connection. " + "Connection ID = " + connectionRef.connectionID); } return connection; } @Override public UUID getID() { return connectionRef.connectionID; } @Override public String toString() { String prefix = "SendToClientAdapter for "; try { return prefix + getConnection(); } catch(IOException e) { return prefix + " FCPPluginConnectionImpl (" + e.getMessage() + ")"; } } } /** * Encapsulates a FCPPluginConnectionImpl object with a default {@link SendDirection} of * {@link SendDirection#ToServer} to implement the send functions which don't require a * direction parameter:<br> * - {@link FCPPluginConnection#send(FCPPluginMessage)}<br> * - {@link FCPPluginConnection#sendSynchronous(FCPPluginMessage, long)}<br><br> * * ATTENTION: Must only be used by the client, not by the server: Client disconnection is * implemented by monitoring the garbage collection of their FCPPluginConnectionImpl objects * - once the connection is not strong referenced anymore, it is considered as closed. * As this class keeps a strong reference to the connection, if servers did use it, they would * prevent client disconnection.<br> * See section "Disconnecting properly" at {@link PluginRespirator#connectToOtherPlugin( * String, ClientSideFCPMessageHandler)}. */ private static final class SendToServerAdapter extends DefaultSendDirectionAdapter { private final FCPPluginConnection parent; /** * For CPU performance of not constructing objects for every {@link #send(FCPPluginMessage)} * please use {@link FCPPluginConnectionImpl#getDefaultSendDirectionAdapter(SendDirection)} * whenever possible to reuse adapters instead of creating new ones with this constructor.*/ SendToServerAdapter(FCPPluginConnectionImpl parent) { super(SendDirection.ToServer); this.parent = parent; } @Override protected FCPPluginConnection getConnection() { return parent; } @Override public UUID getID() { return parent.getID(); } @Override public String toString() { return "SendToServerAdapter for " + parent; } } /** * Returns a {@link DefaultSendDirectionAdapter} (which implements FCPPluginConnection) wrapped * around this FCPPluginConnectionImpl for which the following send functions will then always * send to the {@link SendDirection} which was passed to this function:<br> * - {@link FCPPluginConnection#send(FCPPluginMessage)}<br> * - {@link FCPPluginConnection#sendSynchronous(FCPPluginMessage, long)}<br><br> */ public FCPPluginConnection getDefaultSendDirectionAdapter(SendDirection direction) { return defaultSendDirectionAdapters.get(direction); } /** * @throws NoSendDirectionSpecifiedException * Is always thrown since this function is only implemented for FCPPluginConnectionImpl * objects which are wrapped inside a {@link DefaultSendDirectionAdapter}.<br> * Objects of type FCPPluginConnectionImpl will never be handed out directly to the server * or client application code, they will always be wrapped in such an adapter - so this * function will work for servers and clients. */ @Override public void send(FCPPluginMessage message) { throw new NoSendDirectionSpecifiedException(); } /** * @throws NoSendDirectionSpecifiedException * Is always thrown since this function is only implemented for FCPPluginConnectionImpl * objects which are wrapped inside a {@link DefaultSendDirectionAdapter}.<br> * Objects of type FCPPluginConnectionImpl will never be handed out directly to the server * or client application code, they will always be wrapped in such an adapter - so this * function will work for servers and clients. */ @Override public FCPPluginMessage sendSynchronous(FCPPluginMessage message, long timeoutNanoSeconds) { throw new NoSendDirectionSpecifiedException(); } /** * @see FCPPluginConnectionImpl#send(FCPPluginMessage) * @see FCPPluginConnectionImpl#sendSynchronous(FCPPluginMessage, long) */ @SuppressWarnings("serial") private static final class NoSendDirectionSpecifiedException extends UnsupportedOperationException { public NoSendDirectionSpecifiedException() { super("You must obtain a FCPPluginConnectionImpl with a default SendDirection via " + "getDefaultSendDirectionAdapter() before you may use this function!"); } } @Override public String toString() { return "FCPPluginConnectionImpl (ID: " + id + "; server class: " + serverPluginName + "; server: " + (server != null ? server.get() : null) + "; client: " + client + "; clientConnection: " + clientConnection + ")"; } /** * ATTENTION: For unit test use only. * * @return * The size of the backend table {@link #synchronousSends} of * {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)} */ int getSendSynchronousCount() { synchronousSendsLock.readLock().lock(); try { return synchronousSends.size(); } finally { synchronousSendsLock.readLock().unlock(); } } }