/* 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.pluginmanager;
import java.io.IOException;
import java.util.UUID;
import freenet.clients.fcp.FCPPluginConnection;
import freenet.clients.fcp.FCPPluginConnection.SendDirection;
import freenet.clients.fcp.FCPPluginMessage;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
import freenet.support.io.NativeThread;
/**
* <i>NOTICE: This API is a rewrite of the whole code for plugin communication. It was added
* 2015-03, and for some time after that may change in ways which break backward compatibility.
* Thus any suggestions or pull requests for improvement of all involved interfaces and classes
* are welcome!<br>
* If you would not like to deal with adapting your plugins to possible changes, use the legacy
* {@link FredPluginFCP} API meanwhile.</i><br><br>
*
* FCP server or client plugins which transfer FCP messages to each other using a
* {@link FCPPluginConnection} must implement this interface by implementing one of it's child
* interfaces, to provide a function which handles the received messages.<br><br>
*
* For symmetry, the child interfaces {@link ClientSideFCPMessageHandler} and
* {@link ServerSideFCPMessageHandler} do not provide any different functions.<br>
* They exist nevertheless to allow JavaDoc to explain differences in what the server and client are
* allowed to do.<br>
* You <b>must</b> follow the restrictions which are explained there.<br>
* For clarity, you <b>must</b> implement the child interfaces instead of this interface.<br><br>
*
* If you want to specify the thread priority of the message handling functions, you can
* additionally implement the member interface {@link PrioritizedMessageHandler}.<br><br>
*
* As opposed to the old {@link FredPluginFCP} and {@link FredPluginTalker} message handler
* interfaces, and their {@link PluginReplySender} and {@link PluginTalker} message sending
* counterparts, this new API is as symmetric as possible:<br>
* Both the message handler and message sender is now one interface / class shared by both server
* and client, instead of different ones for each - {@link FredPluginFCPMessageHandler} and
* {@link FCPPluginConnection}.<br>
* With the old interface, the server could only <i>reply</i> to messages of the client, it could
* not send a message without a previous client message.<br>
* With this implementation, server and client are free to send messages to each others whenever
* they like to.<br>
* The only restriction upon this is that the opening and closing of connections is dictated by the
* client. The server cannot connect to a client on its own.
*
* <br><br><h1>Implementing a server</h1><br>
*
* All you have to do to allow clients to connect to your plugin by FCP is implement the child
* interface {@link ServerSideFCPMessageHandler} at your plugin's main class.<br>
* Freenet will then automatically detect that the main class implements it, and allow clients to
* connect by FCP.
*
* <br><br><h1>Implementing a client</h1><br>
*
* Use {@link PluginRespirator#connectToOtherPlugin(String,
* FredPluginFCPMessageHandler.ClientSideFCPMessageHandler)} to connect to a FCP server plugin.
*
* <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 FCPPluginConnection#sendSynchronous(SendDirection, FCPPluginMessage, long)} will not
* deliver replies to the message handler but only return them instead.<br><br>
*
* @author
* xor (xor@freenetproject.org)
* @see FCPPluginConnection
* A connection will be represented as class FCPPluginConnection to the client and server
* plugin. It's JavaDoc provides an overview of the lifecycle of connections.
*/
public interface FredPluginFCPMessageHandler {
/**
* Message handling function for messages received from a plugin FCP server or client.<br/><br/>
*
* <b>ATTENTION</b>: Please read the different constraints for server and client side message
* handlers at the child interfaces:<br/>
* - {@link ServerSideFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection,
* FCPPluginMessage)}<br/>
* - {@link ClientSideFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection,
* FCPPluginMessage)}<br/>
*
* To stress those different constraints, you should also not implement this interface but one
* of the child interfaces {@link ServerSideFCPMessageHandler} and
* {@link ClientSideFCPMessageHandler}.
*/
FCPPluginMessage handlePluginFCPMessage(
FCPPluginConnection connection, FCPPluginMessage message);
/**
* Plugins which provide FCP services to clients must implement this interface.<br/>
* The purpose of this interface is to provide a message handling function for processing
* messages received from the clients.
*
* @see FredPluginFCPMessageHandler
* The parent interface FredPluginFCPMessageHandler provides an overview.
* @see ClientSideFCPMessageHandler
* The opposite version of this interface for client plugins */
public interface ServerSideFCPMessageHandler extends FredPluginFCPMessageHandler {
/**
* <p>Is called to handle messages from your clients.<br/>
* <b>Must not</b> block for very long and thus must only do small amounts of processing.
* </p>
*
* <p>If you ...<br/>
* - Need a long time to compute a reply.<br/>
* - Need to keep a reference to the client connection because you want to send messages to
* the client after having exited this function; maybe even triggered by events at your
* plugin, not by client messages.<br/>
* Then you should:<br/>
* - Store the passed {@link FCPPluginConnection}. If you cannot store objects in memory,
* for example because you are using a database, you can get the {@link UUID} of the
* connection via {@link FCPPluginConnection#getID()}, store only that, and then in the
* future get back the connection using
* {@link PluginRespirator#getPluginConnectionByID(UUID)}.<br>
* - Compute your reply in another thread.</br>
* - Once you're ready to send the reply, send the message using the send functions of the
* {@link FCPPluginConnection}.<br/>
* - Notice that there is no explicit disconnection mechanism. Clients can come and go as
* they please. The only way to be sure that a connection is alive is by checking whether
* the client replies to messages.<br>
* Thus, if you store client connections for longer than sending a single reply, make sure
* to prevent excessive growth of your connection database upon client disconnection
* by implementing a garbage collection mechanism as follows:<br>
* Periodically send a message at each connection and check if you get a reply within a
* reasonable timeout to check whether the connection is still alive. Drop the connection
* if not. You may make a "Ping" message with a "Pong" response a requirement for your
* server's protocol.<br>
* </p>
*
* @param connection
* The connection of the client which sent the message.<br/><br/>
*
* You <b>must not</b> use its send functions for sending back the main reply. Instead,
* use the return value for shipping the reply. (You are free to send "out of band"
* secondary replies using the connection.)<br/>
* The requirement of returning the reply is to ensure that the reply can be clearly
* identified as such, and shipped to the remote side with a clear specification to
* which message it is a reply.<br>
* This is useful for example if the sender of the original message used the
* <i>synchronous</i> send function {@link FCPPluginConnection#sendSynchronous(
* SendDirection, FCPPluginMessage, long)}: The function shall wait for the reply to the
* original message, and return it to the caller. This only works if replies are
* properly identified, otherwise it would have to throw an {@link IOException} to
* signal a timeout while waiting for the reply.<br/><br/>
* @param message
* The actual message. See the JavaDoc of its member variables for an explanation of
* their meaning.
* @return
* Your reply message, or null if you don't want to reply.<br/><br/>
*
* You <b>must</b> construct this by using the constructor {@link FCPPluginMessage#
* constructReplyMessage(FCPPluginMessage, SimpleFieldSet, Bucket, boolean, String,
* String)} (or one of its shortcuts) to ensure that the
* {@link FCPPluginMessage#identifier} gets preserved.<br/><br/>
*
* Replies to replies are not allowed: You <b>must</b> return null if the original
* message was a reply message already as indicated by
* {@link FCPPluginMessage#isReplyMessage()}.<br>
* Replies often shall only indicate success / failure instead of triggering actual
* operations, so it could cause infinite bouncing if you reply to them again.<br/>
* If you still have to send a message to do further operations, you should create a new
* "dialog" by sending an "out of band" message using the passed
* {@link FCPPluginConnection}, as explained in the description of this function.<br>
* Consider the whole of this as a remote procedure call process: A non-reply message is
* the procedure call, a reply message is the procedure result. When receiving the
* result, the procedure call is finished, and shouldn't contain further replies.
* <br><br>
*
* You <b>should</b> always return a reply instead of null if you're allowed to, even if
* you have got nothing to say:<br>
* This allows the remote side to detect whether its requested operation succeeded or
* failed because reply messages always have to specify success/failure.<br>
* Notice: Even upon failure, a reply is better than saying nothing because it allows
* {@link FCPPluginConnection#sendSynchronous(SendDirection, FCPPluginMessage, long)} to
* fail fast instead of having to wait for timeout. */
@Override
FCPPluginMessage handlePluginFCPMessage(
FCPPluginConnection connection, FCPPluginMessage message);
}
/**
* Client plugins which connect to a FCP server plugin must implement this interface.<br/>
* The purpose of this interface is to provide a message handling function for processing
* messages received from the server.
*
* @see FredPluginFCPMessageHandler
* The parent interface FredPluginFCPMessageHandler provides an overview.
* @see ServerSideFCPMessageHandler
* The opposite version of this interface for server plugins */
public interface ClientSideFCPMessageHandler extends FredPluginFCPMessageHandler {
/**
* Is called to handle messages from the server after you sent a message to it using a
* {@link FCPPluginConnection}.<br/><br/>
*
* <b>ATTENTION:</b> The server is free to send messages to you on its own, that is not
* triggered by any message which you sent.<br/>
* This can happen for as long as you keep the connection open by having a hard reference to
* the original {@link FCPPluginConnection} in memory.<br/>
* The purpose of this mechanism is for example to allow the server to tell you about events
* which happened at its side.<br>
*
* @param connection
* The connection which you had originally established to the server.<br/><br/>
*
* You <b>must not</b> use its send functions for sending back the main reply. Instead,
* use the return value for shipping the reply. (You are free to send "out of band"
* secondary replies using the connection.)<br/>
* The requirement of returning the reply is to ensure that the reply can be clearly
* identified as such, and shipped to the remote side with a clear specification to
* which message it is a reply.<br>
* This is useful for example if the sender of the original message used the
* <i>synchronous</i> send function {@link FCPPluginConnection#sendSynchronous(
* SendDirection, FCPPluginMessage, long)}: The function shall wait for the reply to the
* original message, and return it to the caller. This only works if replies are
* properly identified, otherwise it would have to throw an {@link IOException} to
* signal a timeout while waiting for the reply.<br/><br/>
* @param message
* The actual message. See the JavaDoc of its member variables for an explanation of
* their meaning.
* @return
* Your reply message, or null if you don't want to reply.<br/><br/>
*
* You <b>must</b> construct this by using the constructor {@link FCPPluginMessage#
* constructReplyMessage(FCPPluginMessage, SimpleFieldSet, Bucket, boolean, String,
* String)} (or one of its shortcuts) to ensure that the
* {@link FCPPluginMessage#identifier} gets preserved.<br/><br/>
*
* Replies to replies are not allowed: You <b>must</b> return null if the original
* message was a reply message already as indicated by
* {@link FCPPluginMessage#isReplyMessage()}.<br>
* Replies often shall only indicate success / failure instead of triggering actual
* operations, so it could cause infinite bouncing if you reply to them again.<br/>
* If you still have to send a message to do further operations, you should create a new
* "dialog" by sending an "out of band" message using the passed
* {@link FCPPluginConnection}, as explained in the description of this function.<br/>
* Consider the whole of this as a remote procedure call process: A non-reply message is
* the procedure call, a reply message is the procedure result. When receiving the
* result, the procedure call is finished, and shouldn't contain further replies.
* <br><br>
*
* You <b>should</b> always return a reply instead of null if you're allowed to, even
* if you have got nothing to say:<br>
* This allows the remote side to detect whether its requested operation succeeded or
* failed because reply messages always have to specify success/failure.<br>
* Notice: Even upon failure, a reply is better than saying nothing because it allows
* {@link FCPPluginConnection#sendSynchronous(SendDirection, FCPPluginMessage, long)}
* to fail fast instead of having to wait for timeout. */
@Override
FCPPluginMessage handlePluginFCPMessage(
FCPPluginConnection connection, FCPPluginMessage message);
}
/**
* Implement this to specify a thread priority of threads which are used to
* execute the message handling function
* {@link FredPluginFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection,
* FCPPluginMessage)}.<br><br>
*
* Notice that the priority could even be specified depending on the type of individual messages
* as the individual messages are passed to the implementation of this handler. (Of course
* you are free to ignore this parameter and return the same priority for all messages.)
*/
public interface PrioritizedMessageHandler {
/** @see PrioritizedMessageHandler */
public NativeThread.PriorityLevel getPriority(FCPPluginMessage message);
}
}