/*
* Copyright 2007-2010 Brian S O'Neill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cojen.dirmi;
import java.io.Closeable;
import java.io.Flushable;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
/**
* A pipe is a bidirectional stream which can be established via an {@link
* Asynchronous asynchronous} remote method. Pipes can remain open as long as
* the session is open, and all pipes are closed when the session is
* closed. Here's an example remote method declaration which uses a pipe:
*
* <p><pre>
* <b>@Asynchronous</b>
* <b>Pipe</b> uploadFile(String name, <b>Pipe</b> pipe) throws RemoteException;
* </pre>
*
* The remote method declaration requires the return type to be a pipe and one
* parameter must also be a pipe. The client-side invocation of the remote
* method simply passes null for the pipe parameter, and the server-side
* implementation returns null instead of a pipe. Example client call:
*
* <p><pre>
* Pipe pipe = server.uploadFile("notes.txt", null);
* byte[] notes = ...
* pipe.writeInt(notes.length);
* pipe.write(notes);
* pipe.close();
* </pre>
*
* The remote method implementation might look like this:
*
* <p><pre>
* public Pipe uploadFile(String name, Pipe pipe) {
* byte[] notes = new byte[pipe.readInt()];
* pipe.readFully(notes);
* pipe.close();
* ...
* return null;
* }
* </pre>
*
* Pipes are an extension of the remote method invocation itself, which is why
* only one pipe can be established per call. Any arguments which were passed
* along with the pipe are written to the same underlying object stream. For
* long-lived pipes, be sure to call {@link #reset reset} occasionally to allow
* any previously written objects to be freed.
*
* <p>If the pipe is used to pass additional method arguments, consider
* declaring the method with the {@link CallMode#EVENTUAL eventual} calling
* mode. By calling {@link #flush flush} after all arguments are written, the
* number of transported packets is reduced. For the above example, the remote
* method can be declared as:
*
* <pre>
* <b>@Asynchronous(CallMode.EVENTUAL)</b>
* Pipe uploadFile(String name, Pipe pipe) throws RemoteException;
* </pre>
*
* The example client call doesn't need to do anything different, since closing
* the pipe implicitly flushes it. If the server is required to return a value
* over the pipe, then the client call might be written as:
*
* <p><pre>
* Pipe pipe = server.uploadFile("notes.txt", null);
* byte[] notes = ...
* pipe.writeInt(notes.length);
* pipe.write(notes);
* // Flush to ensure arguments and file are transported.
* pipe.flush();
* // Read server response.
* Object response = pipe.readObject();
* pipe.close();
* </pre>
*
* Methods which use pipes may also utilize {@link Timeout timeout}
* annotations. A timeout task is started when the remote method is invoked,
* and it must be explicitly {@link #cancelTimeout cancelled} upon receiving
* the pipe. Timeout tasks may also be explicitly {@link #startTimeout started}
* without requiring the annotation.
*
* <p>Because pipes are bidirectional, extra communication overhead is required
* to support safe connection recycling. The {@link CallMode#REQUEST_REPLY
* request/reply} call mode requires less overhead, and is more suitable for
* short-lived pipes. A request/reply pipe starts in "request" mode, and then
* it switches to "reply" mode. Initially, the client can write to the pipe,
* and the server can read from the pipe. As soon as the client performs a
* read, it automatically flushes the pipe and then it can only perform further
* reads. Attempting to write again causes an {@code IOException} to be thrown.
* Likewise, as soon as the server performs a write, it cannot read again. As
* with normal pipes, both endpoints must close the pipe in order for the
* connection resource to be fully recycled.
*
* <p>Request/reply pipes cannot be efficiently closed by the remote server until the pipe
* is in reply mode. Closing the pipe prematurely requires that all request input be
* automatically drained. If the request is of an unbounded size, then the server has no
* means to effectively abort it. Normal pipes should be used for this case instead.
*
* @author Brian S O'Neill
*/
public interface Pipe extends Flushable, Closeable, ObjectInput, ObjectOutput, Link {
/**
* Returns the pipe's InputStream which also implements ObjectInput.
* Closing the stream is equivalent to closing the pipe.
*/
InputStream getInputStream();
/**
* Returns the pipe's OutputStream which also implements ObjectOutput.
* Closing the stream is equivalent to closing the pipe.
*/
OutputStream getOutputStream();
/**
* Reads a Throwable which was written via writeThrowable, which may be null.
*/
Throwable readThrowable() throws IOException, ReconstructedException;
/**
* Writes the given Throwable with additional remote information. Throwable
* may be null.
*/
void writeThrowable(Throwable t) throws IOException;
/**
* Disregard the state of any objects already written to the pipe, allowing
* them to get freed.
*/
void reset() throws IOException;
/**
* Flushes the pipe by writing any buffered output to the transport layer.
*/
void flush() throws IOException;
/**
* Starts a task which forcibly closes this pipe after the timeout has
* elapsed, unless it is {@link #cancelTimeout cancelled} in time. If a
* timeout is already in progress when this method is called, it is
* replaced with a new timeout.
*
* @return false if task could not be started because existing timeout
* could not be cancelled or pipe is closed
* @throws IOException if task could not be scheduled
*/
boolean startTimeout(long timeout, TimeUnit unit) throws IOException;
/**
* Cancels a timeout task which was started by {@link #startTimeout
* startTimeout} or an initial timeout when using the {@link
* CallMode#EVENTUAL eventual} calling mode. If no timeout task exists when
* this method is called, it does nothing and returns true.
*
* @return false if too late to cancel task and pipe will be closed
*/
boolean cancelTimeout();
/**
* Closes both the input and output of the pipe. Any buffered output is
* flushed first.
*/
void close() throws IOException;
}