/* 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();
}
}
}