/* 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.util.UUID;
import java.util.concurrent.TimeUnit;
import freenet.pluginmanager.FredPluginFCPMessageHandler;
import freenet.pluginmanager.FredPluginFCPMessageHandler.ClientSideFCPMessageHandler;
import freenet.pluginmanager.FredPluginFCPMessageHandler.ServerSideFCPMessageHandler;
import freenet.pluginmanager.PluginRespirator;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
/**
* An FCP connection between:<br>
* - a fred plugin which provides its services by a FCP server.<br>
* - a client application which uses those services. The client may be a plugin as well, or be
* connected by a networked FCP connection.<br><br>
*
* <h1>How to use this properly</h1><br>
*
* You can read the following JavaDoc for a nice overview of how to use this properly from the
* perspective of your server or client implementation:<br>
* - {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}<br>
* - {@link PluginRespirator#getPluginConnectionByID(UUID)}<br>
* - {@link FredPluginFCPMessageHandler}<br>
* - {@link FredPluginFCPMessageHandler.ServerSideFCPMessageHandler}<br>
* - {@link FredPluginFCPMessageHandler.ClientSideFCPMessageHandler}<br>
* - {@link FCPPluginMessage}<br><br>
*
* <h1>Debugging</h1><br>
*
* You can configure the {@link Logger} to log "freenet.clients.fcp.FCPPluginConnection:DEBUG" to
* cause logging of all sent and received messages.<br>
* This is usually done on the Freenet web interface at Configuration / Logs / Detailed priority
* thresholds.<br>
* ATTENTION: The log entries will appear at the time when the messages were queued for sending, not
* when they were delivered. Delivery usually happens in a separate thread. Thus, the relative order
* of arrival of messages can be different to the order of their appearance in the log file.<br>
* If you need to know the order of arrival, add logging to your message handler. Also don't forget
* that {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)} will not deliver replies
* to the message handler but only return them instead.<br><br>
*
*
* <h1>Connection lifecycle</h1>
* <h2>Intra-node FCP - server and client both running within the same node as plugin</h2>
* The client plugin dictates connection and disconnection. Connections are opened by it via
* {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}. It keeps
* them open by keeping a strong reference to the FCPPluginConnection. Once it does not strongly
* reference it anymore, Freenet detects that by monitoring garbage collection, and considers the
* connection as closed then. A closed connection is indicated to the server plugin by the send
* functions throwing {@link IOException}.<br>
* <h2>Networked FCP - server is running in the node as plugin, client connects by network</h2>
* The client plugin again dictates connection and disconnection by opening and closing the FCP
* network connection as it pleases.<br>
* Additionally, a single network connection can only have a single FCPPluginConnection to each
* server plugin.<br>
* If you nevertheless need multiple connections to a plugin, you have to create multiple
* network connections to the FCP server.<br>
* (The reason for this is the following: Certain server plugins might need to store the
* {@link UUID} of a client connection in their database so they are able to send data to the client
* if an event of interest to the client happens in the future. Therefore, the {@link UUID} of a
* connection must not change during the lifetime of the connection. To ensure a permanent
* {@link UUID} of a connection, only a single FCPPluginConnection can exist per server plugin per
* network connection).
*
* <h2>Persistence</h2>
* <p>
* In opposite to a FCP connection to fred itself, which is represented by
* {@link PersistentRequestClient} and can exist across restarts, a FCPPluginConnection is kept in
* existence by fred only while the actual client is connected. In case of networked plugin FCP,
* this is while the parent network connection is open; or in case of non-networked plugin
* FCP, while the FCPPluginConnection is strong-referenced by the client plugin.<br>
* There is no such thing as persistence beyond client disconnection / restarts.<br/>
* This was decided to simplify implementation:<br/>
* - Persistence should have been implemented by using the existing persistence framework of
* {@link PersistentRequestClient}. That would require extending the class though, and it is a
* complex class. The work for extending it was out of scope of the time limit for implementing
* this class.<br/>
* - FCPPluginConnection instances need to be created without a network connection for intra-node
* plugin connections. If we extended class {@link PersistentRequestClient}, a lot of care would
* have to be taken to allow it to exist without a network connection - that would even be more
* work.<br/>
* </p>
*
* <h1>Internals</h1><br>
*
* If you plan to work on the fred-side implementation of FCP plugin connections, please see the
* "Internals" section at the implementation {@link FCPPluginConnectionImpl} of this interface.
* Notably, the said section provides an overview of the flow of messages.<br><br>
*
* @author xor (xor@freenetproject.org)
*/
public interface FCPPluginConnection {
/**
* The send functions are fully symmetrical: They work the same way no matter whether client
* is sending to server or server is sending to client.<br/>
* Thus, to prevent us from having to duplicate the send functions, this enum specifies in which
* situation we are.
*/
public static enum SendDirection {
ToServer,
ToClient;
public final SendDirection invert() {
return (this == ToServer) ? ToClient : ToServer;
}
}
/**
* Can be used by both server and client implementations to send messages to each other.<br>
* The messages sent by this function will be delivered to the remote side at either:
* - the message handler {@link FredPluginFCPMessageHandler#
* handlePluginFCPMessage(FCPPluginConnection, FCPPluginMessage)}.<br>
* - or, if existing, a thread waiting for a reply message in
* {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)}.<br><br>
*
* This is an <b>asynchronous</b>, non-blocking send function.<br>
* This has the following differences to the blocking send {@link #sendSynchronous(
* SendDirection, FCPPluginMessage, long)}:<br>
* - It may return <b>before</b> the message has been sent.<br>
* The message sending happens in another thread so this function can return immediately.<br>
* In opposite to that, a sendSynchronous() would wait for a reply to arrive, so once it
* returns, the message is guaranteed to have been sent.<br>
* - The reply is delivered to your message handler {@link FredPluginFCPMessageHandler}. It will
* not be directly available to the thread which called this function.<br>
* A sendSynchronous() would return the reply to the caller.<br>
* - You have no guarantee whatsoever that the message will be delivered.<br>
* A sendSynchronous() will tell you that a reply was received, which guarantees that the
* message was delivered.<br>
* - The order of arrival of messages is random.<br>
* A sendSynchronous() only returns after the message was delivered already, so by calling
* it multiple times in a row on the same thread, you would enforce the order of the
* messages arriving at the remote side.<br><br>
*
* ATTENTION: The consequences of this are:<br>
* - Even if the function returned without throwing an {@link IOException} you nevertheless must
* <b>not</b> assume that the message has been sent.<br>
* - If the function did throw an {@link IOException}, you <b>must</b> assume that the
* connection is dead and the message has not been sent.<br>
* You <b>must</b> consider this FCPPluginConnection as dead then and create a fresh one.<br>
* - You can only be sure that a message has been delivered if your message handler receives
* a reply message with the same value of
* {@link FCPPluginMessage#identifier} as the original message.<br>
* - You <b>can</b> send many messages in parallel by calling this many times in a row.<br>
* But you <b>must not</b> call this too often in a row to prevent excessive threads creation.
* <br><br>
*
* ATTENTION: If you plan to use this inside of message handling functions of your
* implementations of the interfaces
* {@link FredPluginFCPMessageHandler.ServerSideFCPMessageHandler} or
* {@link FredPluginFCPMessageHandler.ClientSideFCPMessageHandler}, be sure to read the JavaDoc
* of the message handling functions first as it puts additional constraints on the usage
* of the FCPPluginConnection they receive.
*
* @param direction
* Whether to send the message to the server or the client message handler.<br>
* You <b>can</b> use this to send messages to yourself.<br>
* You may use {@link #send(FCPPluginMessage)} to avoid having to specify this.<br><br>
*
* @param message
* You <b>must not</b> send the same message twice: This can break {@link #sendSynchronous(
* SendDirection, FCPPluginMessage, long)}.<br>
* To ensure this, always construct a fresh FCPPluginMessage object when re-sending a
* message. If you use the constructor which allows specifying your own identifier, always
* generate a fresh, random identifier.<br>
* TODO: Code quality: Add a flag to FCPPluginMessage which marks the message as sent and
* use it to log an error if someone tries to send the same message twice.
* <br><br>
*
* @throws IOException
* If the connection has been closed meanwhile.<br/>
* This FCPPluginConnection <b>should be</b> considered as dead once this happens, you
* should then discard it and obtain a fresh one.
*
* <p><b>ATTENTION:</b> If this is not thrown, that does NOT mean that the connection is
* alive. Messages are sent asynchronously, so it can happen that a closed connection is not
* detected before this function returns.<br/>
* The only way of knowing that a send succeeded is by receiving a reply message in your
* {@link FredPluginFCPMessageHandler}.<br>
* If you need to know whether the send succeeded on the same thread which shall call the
* send function, you can also use {@link #sendSynchronous(SendDirection, FCPPluginMessage,
* long)} which will return the reply right away.</p>
* @see #sendSynchronous(SendDirection, FCPPluginMessage, long)
* You may instead use the blocking sendSynchronous() if your thread needs to know whether
* messages arrived, to ensure a certain order of arrival, or to know the reply to a
* message.
*/
public void send(SendDirection direction, FCPPluginMessage message) throws IOException;
/**
* Same as {@link #send(SendDirection, FCPPluginMessage)} with the {@link SendDirection}
* parameter being the default direction.<br>
* <b>Please do read its JavaDoc as it contains a very precise specification how to use it and
* thereby also this function.</b><br><br>
*
* The default direction is determined automatically depending on whether you are a client or
* server.<br><br>
*
* You are acting as a client, and thus by default send to the server, when you obtain a
* FCPPluginConnection...:<br>
* - from the return value of
* {@link PluginRespirator#connectToOtherPlugin(String, ClientSideFCPMessageHandler)}.<br>
* - as parameter to your message handler callback {@link ClientSideFCPMessageHandler#
* handlePluginFCPMessage(FCPPluginConnection, FCPPluginMessage)}.<br><br>
*
* You are acting as a server, and thus by default send to the client, when you obtain a
* FCPPluginConnection...:<br>
* - as parameter to your message handler callback {@link ServerSideFCPMessageHandler#
* handlePluginFCPMessage(FCPPluginConnection, FCPPluginMessage)}.<br>
* - from the return value of {@link PluginRespirator#getPluginConnectionByID(UUID)}.<br>
*/
public void send(FCPPluginMessage message) throws IOException;
/**
* Can be used by both server and client implementations to send messages in a blocking
* manner to each other.<br>
* The messages sent by this function will be delivered to the message handler
* {@link FredPluginFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection,
* FCPPluginMessage)} of the remote side.<br><br>
*
* This has the following differences to a regular non-synchronous
* {@link #send(SendDirection, FCPPluginMessage)}:<br>
* - It will <b>wait</b> for a reply message of the remote side before returning.<br>
* A regular send() would instead queue the message for sending, and then return immediately.
* - The reply message will be <b>returned to the calling thread</b> instead of being passed to
* the message handler {@link FredPluginFCPMessageHandler#handlePluginFCPMessage(
* FCPPluginConnection, FCPPluginMessage)} in another thread.<br>
* NOTICE: It is possible that the reply message <b>is</b> passed to the message handler
* upon certain error conditions, for example if the timeout you specify when calling this
* function expires before the reply arrives. This is not guaranteed though.<br>
* - Once this function returns without throwing, it is <b>guaranteed</b> that the message has
* arrived at the remote side.<br>
* - The <b>order</b> of messages can be preserved: If you call sendSynchronous() twice in a
* row, the second call cannot execute before the first one has returned, and the returning
* of the first call guarantees that the first message was delivered already.<br>
* Regular send() calls deploy each message in a thread. This means that the order of delivery
* can be different than the order of sending.<br><br>
*
* ATTENTION: This function can cause the current thread to block for a long time, while
* bypassing the thread limit. Therefore, only use this if the desired operation at the remote
* side is expected to execute quickly and the thread which sends the message <b>immediately</b>
* needs one of these after sending it to continue its computations:<br>
* - An guarantee that the message arrived at the remote side.<br>
* - An indication of whether the operation requested by the message succeeded.<br>
* - The reply to the message.<br>
* - A guaranteed order of arrival of messages at the remote side.<br>
* A typical example for a place where this is needed is a user interface which has a user
* click a button and want to see the result of the operation as soon as possible. A detailed
* example is given at the documentation of the return value below.<br>
* Notice that even this could be done asynchronously with certain UI frameworks: An event
* handler could wait asynchronously for the result and fill it in the UI. However, for things
* such as web interfaces, you might need JavaScript then, so a synchronous call will simplify
* the code.<br>
* In addition to only using synchronous calls when absolutely necessary, please make sure to
* set a timeout parameter which is as small as possible.<br><br>
*
* ATTENTION: While remembering that this function can block for a long time, you have to
* consider that this class will <b>not</b> call {@link Thread#interrupt()} upon pending calls
* to this function during shutdown. You <b>must</b> keep track of threads which are executing
* this function on your own, and call {@link Thread#interrupt()} upon them at shutdown of your
* plugin. The interruption will then cause the function to throw {@link InterruptedException}
* quickly, which your calling threads should obey by exiting to ensure a fast shutdown.<br><br>
*
* ATTENTION: This function can only work properly as long the message which you passed to this
* function does contain a message identifier which does not collide with one of another
* message.<br>
* To ensure this, you <b>must</b> use the constructor {@link FCPPluginMessage#construct(
* SimpleFieldSet, Bucket)} (or one of its shortcuts) and do not call this function twice upon
* the same message.<br>
* If you do not follow this rule and use colliding message identifiers, there might be side
* effects such as:<br>
* - This function might return the reply to the colliding message instead of the reply to
* your message. Notice that this implicitly means that you cannot be sure anymore that
* a message was delivered successfully if this function does not throw.<br>
* - The reply might be passed to the {@link FredPluginFCPMessageHandler} instead of being
* returned from this function.<br>
* Please notice that both these side effects can also happen if the remote partner erroneously
* sends multiple replies to the same message identifier.<br>
* As long as the remote side is implemented using FCPPluginConnection as well, and uses it
* properly, this shouldn't happen though. Thus in general, you should assume that the reply
* which this function returns <b>is</b> the right one, and your
* {@link FredPluginFCPMessageHandler} should just drop reply messages which were not expected
* and log them as at {@link LogLevel#WARNING}. The information here was merely provided to help
* you with debugging the cause of these events, <b>not</b> to make you change your code
* to assume that sendSynchronous does not work. For clean code, please write it in a way which
* assumes that the function works properly.<br><br>
*
* ATTENTION: If you plan to use this inside of message handling functions of your
* implementations of the interfaces
* {@link FredPluginFCPMessageHandler.ServerSideFCPMessageHandler} or
* {@link FredPluginFCPMessageHandler.ClientSideFCPMessageHandler}, be sure to read the JavaDoc
* of the message handling functions first as it puts additional constraints on the usage
* of the FCPPluginConnection they receive.<br><br>
*
* @param direction
* Whether to send the message to the server or the client message handler.<br>
* You <b>can</b> use this to send messages to yourself.<br>
* You may use {@link #sendSynchronous(FCPPluginMessage, long)} to avoid having to specify
* this.<br><br>
*
* @param message
* <b>Must be</b> constructed using {@link FCPPluginMessage#construct(SimpleFieldSet,
* Bucket)} or one of its shortcuts.<br><br>
*
* Must <b>not</b> be a reply message: This function needs determine when the remote side
* has finished processing the message so it knows when to return. That requires the remote
* side to send a reply to indicate that the FCP call is finished. Replies to replies are
* not allowed though (to prevent infinite bouncing).<br><br>
*
* @param timeoutNanoSeconds
* The function will wait for a reply to arrive for this amount of time.<br>
* Must be greater than 0 and below or equal to 1 minute.<br><br>
*
* If the timeout expires, an {@link IOException} is thrown.<br>
* This FCPPluginConnection <b>should be</b> considered as dead once this happens, you
* should then discard it and obtain a fresh one.<br><br>
*
* ATTENTION: The sending of the message is not affected by this timeout, it only affects
* how long we wait for a reply. The sending is done in another thread, so if your message
* is very large, and takes longer to transfer than the timeout grants, this function will
* throw before the message has been sent.<br>
* Additionally, the sending of the message is <b>not</b> terminated if the timeout expires
* before it was fully transferred. Thus, the message can arrive at the remote side even if
* this function has thrown, and you might receive an off-thread reply to the message in the
* {@link FredPluginFCPMessageHandler}.<br><br>
*
* Notice: For convenience, use class {@link TimeUnit} to easily convert seconds,
* milliseconds, etc. to nanoseconds.<br><br>
*
* @return
* The reply {@link FCPPluginMessage} which the remote partner sent to your message.<br><br>
*
* <b>ATTENTION</b>: Even if this function did not throw, the reply might indicate an error
* with the field {link FCPPluginMessage#success}: This can happen if the message was
* delivered but the remote message handler indicated that the FCP operation you initiated
* failed.<br>
* The fields {@link FCPPluginMessage#errorCode} and {@link FCPPluginMessage#errorMessage}
* might indicate the type of the error.<br><br>
*
* This can be used to decide to retry certain operations. A practical example would be a
* user trying to create an account at an FCP server application:<br>
* - Your UI would use this function to try to create the account by FCP.<br>
* - The user might type an invalid character in the username.<br>
* - The server could then indicate failure of creating the account by sending a reply with
* success == false.<br>
* - Your UI could detect the problem by success == false at the reply and an errorCode of
* "InvalidUsername". The errorCode can be used to decide to highlight the username field
* with a red color.<br>
* - The UI then could prompt the user to chose a valid username by displaying the
* errorMessage which the server provides to ship a translated, human readable explanation
* of what is wrong with the username.<br>
* @throws IOException
* If the given timeout expired before a reply was received <b>or</b> if the connection has
* been closed before even sending the message.<br>
* This FCPPluginConnection <b>should be</b> considered as dead once this happens, you
* should then discard it and obtain a fresh one.
* @throws InterruptedException
* If another thread called {@link Thread#interrupt()} upon the thread which you used to
* execute this function.<br>
* This is a shutdown mechanism: You can use it to abort a call to this function which is
* waiting for the timeout to expire.<br><br>
* @see FCPPluginConnectionImpl#synchronousSends
* An overview of how synchronous sends and especially their threading work internally is
* provided at the map which stores them.
* @see #send(SendDirection, FCPPluginMessage)
* The non-blocking, asynchronous send() should be used instead of this whenever possible.
*/
public FCPPluginMessage sendSynchronous(
SendDirection direction, FCPPluginMessage message, long timeoutNanoSeconds)
throws IOException, InterruptedException;
/**
* Same as {@link #sendSynchronous(SendDirection, FCPPluginMessage, long)} with the
* {@link SendDirection} parameter being the default direction.<br>
* <b>Please do read its JavaDoc as it contains a very precise specification how to use it and
* thereby also this function.</b><br><br>
*
* For an explanation of how the default send direction is determined, see
* {@link #send(FCPPluginMessage)}.
*/
public FCPPluginMessage sendSynchronous(FCPPluginMessage message, long timeoutNanoSeconds)
throws IOException, InterruptedException;
/**
* @return A unique identifier among all FCPPluginConnections.
* @see The ID can be used with {@link PluginRespirator#getPluginConnectionByID(UUID)}.
*/
public UUID getID();
/** @return A verbose String containing the internal state. Useful for debug logs. */
public String toString();
}