/***************************************************************************
* Copyright (C) 2006-2008 by Fabrizio Montesi <famontesi@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.net;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantLock;
import jolie.net.ports.InputPort;
import jolie.net.ports.OutputPort;
import jolie.net.ports.Port;
import jolie.runtime.TimeoutHandler;
/**
* <code>CommChannel</code> allows for the sending and receiving of <code>CommMessage</code> instances.
* This class is thread-safe.
*
* This abstract class is meant to be extended by classes implementing the
* communication logic for sending and receiving messages.
*
* Either the {@link #disposeForInput() disposeForInput} or the {@link #release() release} method must be called after using
* a channel. Their behaviour can be influenced by indicating if a channel
* is to be closed or not through the {@link #setToBeClosed(boolean) setToBeClosed} method, but this does not
* give any right to make assumptions on said behaviour: implementing classes
* have complete freedom on that.
* @author Fabrizio Montesi
* @see CommMessage
*/
public abstract class CommChannel
{
protected final ReentrantLock lock = new ReentrantLock( true );
private boolean toBeClosed = true;
private InputPort inputPort = null;
private OutputPort outputPort = null;
private boolean isOpen = true;
private long redirectionMessageId = 0L;
private TimeoutHandler timeoutHandler = null;
protected void setTimeoutHandler( TimeoutHandler timeoutHandler )
{
this.timeoutHandler = timeoutHandler;
}
protected TimeoutHandler timeoutHandler()
{
return timeoutHandler;
}
protected long redirectionMessageId()
{
return redirectionMessageId;
}
protected void setRedirectionMessageId( long id )
{
redirectionMessageId = id;
}
private CommChannel redirectionChannel = null;
/**
* Returns <code>true</code> if this channel is to be closed when not used,
* <code>false</code> otherwise.
* @return <code>true</code> if this channel is to be closed when not used,
* <code>false</code> otherwise.
*/
public final boolean toBeClosed()
{
return toBeClosed;
}
public CommChannel createDuplicate()
{
throw new IllegalAccessError( "Only local channels can be duplicated" );
}
/**
* Sets a redirection channel for this channel.
* When a <code>CommChannel</code> has a redirection channel set,
* the next input message received by the <code>CommCore</code> on that
* channel will be redirected automatically to the redirection channel
* that has been set.
* @param redirectionChannel the redirection channel to set
*/
public void setRedirectionChannel( CommChannel redirectionChannel )
{
this.redirectionChannel = redirectionChannel;
}
/**
* Returns the redirection channel of this channel.
* @return the redirection channel of this channel
*/
public CommChannel redirectionChannel()
{
return redirectionChannel;
}
/**
* Sets the parent {@link InputPort} of this channel.
* @param listener the parent {@link InputPort} of this channel.
*/
public void setParentInputPort( InputPort inputPort )
{
this.inputPort = inputPort;
}
/**
* Returns the parent {@link InputPort} of this channel.
* @return the parent {@link InputPort} of this channel.
*/
public InputPort parentInputPort()
{
return inputPort;
}
/**
* Sets the parent {@link OutputPort} of this channel.
* @param listener the parent {@link OutputPort} of this channel.
*/
public void setParentOutputPort( OutputPort outputPort )
{
this.outputPort = outputPort;
}
/**
* Returns the parent {@link OutputPort} of this channel.
* @return the parent {@link OutputPort} of this channel.
*/
public OutputPort parentOutputPort()
{
return outputPort;
}
/**
* Returns the parent {@link Port} of this channel.
* @return the parent {@link Port} of this channel.
*/
public Port parentPort()
{
return ( inputPort == null ) ? outputPort : inputPort;
}
/**
* Returns <code>true</code> if this channel is open, <code>false</code> otherwise.
* @return <code>true</code> if this channel is open, <code>false</code> otherwise
*/
public final boolean isOpen()
{
return isOpen && isOpenImpl();
}
protected boolean isOpenImpl()
{
return true;
}
protected boolean isThreadSafe()
{
return false;
}
/**
* Receives a message from the channel. This is a blocking operation.
* @return the received message
* @throws IOException in case of some communication error
*/
public CommMessage recv()
throws IOException
{
CommMessage ret;
if ( lock.isHeldByCurrentThread() ) {
ret = recvImpl();
} else {
lock.lock();
try {
ret = recvImpl();
} finally {
lock.unlock();
}
}
return ret;
}
/**
* Receives a response for the specified request.
* @param request the request message for which we want to receive a response
* @return the response for the specified request message
* @throws java.io.IOException in case of some communication error
*/
public abstract CommMessage recvResponseFor( CommMessage request )
throws IOException;
/**
* Sends a message through this channel.
* @param message the message to send
* @throws java.io.IOException in case of some communication error
*/
public void send( CommMessage message )
throws IOException
{
if ( lock.isHeldByCurrentThread() ) {
sendImpl( message );
} else {
lock.lock();
try {
sendImpl( message );
} finally {
lock.unlock();
}
}
}
protected abstract CommMessage recvImpl()
throws IOException;
protected abstract void sendImpl( CommMessage message )
throws IOException;
/**
* Releases this CommChannel, making it available
* to other processes for sending data.
* @throws IOException in case of an internal error
*
*/
public final void release()
throws IOException
{
if ( lock.isHeldByCurrentThread() ) {
if ( toBeClosed() ) {
isOpen = false;
close();
} else {
releaseImpl();
}
} else {
lock.lock();
try {
if ( toBeClosed() ) {
isOpen = false;
close();
} else {
releaseImpl();
}
} finally {
lock.unlock();
}
}
}
protected void releaseImpl()
throws IOException
{
isOpen = false;
closeImpl();
}
/**
* Disposes this channel for input.
* This method can behave in two ways, depending on the state of the channel
* and its underlying implementation:
* <ul><li>
* the channel is closed and its resources are released;
* </li><li>
* the channel is kept open and its control is given to its generating
* <code>CommCore</code> instance, which will listen for input messages
* on this channel.
* </li></ul>
* @throws java.io.IOException in case of some error generated by this channel
* implementation
*/
public final void disposeForInput()
throws IOException
{
if ( lock.isHeldByCurrentThread() ) {
if ( toBeClosed() == false ) {
disposeForInputImpl();
}
} else {
lock.lock();
try {
if ( toBeClosed() == false ) {
disposeForInputImpl();
}
} finally {
lock.unlock();
}
}
}
protected void disposeForInputImpl()
throws IOException
{}
/**
* Sets if this channel is to be closed after releasing or not.
* @param toBeClosed <code>true</code> if this channel is to be closed after releasing, <code>false</code> otherwise
*/
public void setToBeClosed( boolean toBeClosed )
{
this.toBeClosed = toBeClosed;
}
protected void close()
throws IOException
{
closeImpl();
}
/** Implements the communication channel closing operation. */
protected abstract void closeImpl()
throws IOException;
}