/***************************************************************************
* Copyright (C) 2006-2011 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;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import jolie.lang.Constants;
import jolie.net.SessionMessage;
import jolie.process.Process;
import jolie.process.TransformationReason;
import jolie.runtime.ExitingException;
import jolie.runtime.FaultException;
import jolie.runtime.InputOperation;
import jolie.runtime.Value;
import jolie.runtime.VariablePath;
import jolie.runtime.VariablePathBuilder;
import jolie.runtime.correlation.CorrelationSet;
import jolie.util.Pair;
/**
* An ExecutionThread representing a session, equipped
* with a dedicated state and message queue.
* @author Fabrizio Montesi
*/
public class SessionThread extends ExecutionThread
{
private class SessionMessageFuture implements Future< SessionMessage >
{
private final Lock lock;
private final Condition condition;
private SessionMessage sessionMessage = null;
private boolean isDone = false;
private boolean isCancelled = false;
public SessionMessageFuture()
{
lock = new ReentrantLock();
condition = lock.newCondition();
}
public boolean cancel( boolean mayInterruptIfRunning )
{
lock.lock();
try {
if ( !isDone ) {
this.sessionMessage = null;
isDone = true;
isCancelled = true;
condition.signalAll();
}
} finally {
lock.unlock();
}
return true;
}
public SessionMessage get( long timeout, TimeUnit unit )
throws InterruptedException, TimeoutException
{
try {
lock.lock();
if ( !isDone ) {
if ( !condition.await( timeout, unit ) ) {
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
return sessionMessage;
}
public SessionMessage get()
throws InterruptedException
{
try {
lock.lock();
if ( !isDone ) {
condition.await();
}
} finally {
lock.unlock();
}
return sessionMessage;
}
public boolean isCancelled()
{
return isCancelled;
}
public boolean isDone()
{
return isDone;
}
protected void setResult( SessionMessage sessionMessage )
{
lock.lock();
try {
if ( !isDone ) {
this.sessionMessage = sessionMessage;
isDone = true;
condition.signalAll();
}
} finally {
lock.unlock();
}
}
}
private class SessionMessageNDFuture extends SessionMessageFuture
{
private final String[] operationNames;
public SessionMessageNDFuture( String[] operationNames )
{
super();
this.operationNames = operationNames;
}
@Override
protected void setResult( SessionMessage sessionMessage )
{
for( String operationName : operationNames ) {
if ( operationName.equals( sessionMessage.message().operationName() ) == false ) {
Deque< SessionMessageFuture > waitersList = messageWaiters.get( operationName );
if ( waitersList != null ) {
waitersList.remove( this );
}
}
}
super.setResult( sessionMessage );
}
}
private final jolie.State state;
private final List< SessionListener > listeners = new LinkedList< SessionListener >();
protected final Map< CorrelationSet, Deque< SessionMessage > > messageQueues =
new HashMap< CorrelationSet, Deque< SessionMessage > >();
protected final Deque< SessionMessage > uncorrelatedMessageQueue = new LinkedList< SessionMessage >();
private final Map< String, Deque< SessionMessageFuture > > messageWaiters =
new HashMap< String, Deque< SessionMessageFuture > >();
private final static VariablePath typeMismatchPath;
private final static VariablePath ioExceptionPath;
static {
typeMismatchPath =
new VariablePathBuilder( false )
.add( "main", 0 )
.add( Constants.TYPE_MISMATCH_FAULT_NAME, 0 )
.toVariablePath();
ioExceptionPath =
new VariablePathBuilder( false )
.add( "main", 0 )
.add( Constants.IO_EXCEPTION_FAULT_NAME, 0 )
.add( "stackTrace", 0 )
.toVariablePath();
}
/**
* Creates and returns a default list of handlers, initialized
* with default fault handlers for built-in faults like, e.g., TypeMismatch.
* @param interpreter the <code>Interpreter</code> in which the returned map will be used
* @return a newly created default list of handlers
*/
public static List< Pair< String, Process > > createDefaultFaultHandlers( final Interpreter interpreter )
{
final List< Pair< String, Process > > instList = new ArrayList< Pair< String, Process > >();
instList.add( new Pair< String, Process >(
Constants.TYPE_MISMATCH_FAULT_NAME,
new Process() {
public void run() throws FaultException, ExitingException
{
interpreter.logWarning( typeMismatchPath.getValue().strValue() );
}
public Process clone( TransformationReason reason )
{
return this;
}
public boolean isKillable()
{
return true;
}
}
) );
instList.add( new Pair< String, Process >(
Constants.IO_EXCEPTION_FAULT_NAME,
new Process() {
public void run() throws FaultException, ExitingException
{
interpreter.logWarning( ioExceptionPath.getValue().strValue() );
}
public Process clone( TransformationReason reason )
{
return this;
}
public boolean isKillable()
{
return true;
}
}
) );
return instList;
}
private SessionThread( Process process, ExecutionThread parent, jolie.State state )
{
super( process, parent );
this.state = state;
initMessageQueues();
}
public SessionThread( Process process, jolie.State state, ExecutionThread parent )
{
super( parent.interpreter(), process );
this.state = state;
for( Scope s : parent.scopeStack ) {
scopeStack.push( s.clone() );
}
initMessageQueues();
}
public boolean isInitialisingThread()
{
return false;
}
/**
* Registers a <code>SessionListener</code> for receiving events from this
* session.
* @param listener the <code>SessionListener</code> to register
*/
public void addSessionListener( SessionListener listener )
{
listeners.add( listener );
}
/**
* Constructs a SessionThread with a fresh State.
* @param interpreter the Interpreter this thread must refer to
* @param process the Process this thread has to execute
*/
public SessionThread( Interpreter interpreter, Process process )
{
super( interpreter, process );
state = new jolie.State();
initMessageQueues();
}
private void initMessageQueues()
{
for( CorrelationSet cset : interpreter().correlationSets() ) {
messageQueues.put( cset, new LinkedList< SessionMessage >() );
}
}
/**
* Constructs a SessionThread cloning another ExecutionThread, copying the
* State and Scope stack of the parent.
*
* @param process the Process this thread has to execute
* @param parent the ExecutionThread to copy
* @param notifyProc the CorrelatedProcess to notify when this session expires
* @see CorrelatedProcess
*/
public SessionThread( Process process, ExecutionThread parent )
{
super( process, parent );
initMessageQueues();
assert( parent != null );
state = parent.state().clone();
for( Scope s : parent.scopeStack ) {
scopeStack.push( s.clone() );
}
}
/**
* Returns the State of this thread.
* @return the State of this thread
* @see State
*/
public jolie.State state()
{
return state;
}
public Future< SessionMessage > requestMessage( Map< String, InputOperation > operations, ExecutionThread ethread )
{
SessionMessageFuture future = new SessionMessageNDFuture( operations.keySet().toArray( new String[0] ) );
ethread.cancelIfKilled( future );
synchronized( messageQueues ) {
Deque< SessionMessage > queue = null;
SessionMessage message = null;
InputOperation operation = null;
Iterator< Deque< SessionMessage > > it = messageQueues.values().iterator();
while( operation == null && it.hasNext() ) {
queue = it.next();
message = queue.peekFirst();
if ( message != null ) {
operation = operations.get( message.message().operationName() );
}
}
if ( message == null ) {
queue = uncorrelatedMessageQueue;
message = queue.peekFirst();
if ( message != null ) {
operation = operations.get( message.message().operationName() );
}
}
if ( message == null || operation == null ) {
for( Map.Entry< String, InputOperation > entry : operations.entrySet() ) {
addMessageWaiter( entry.getValue(), future );
}
} else {
future.setResult( message );
queue.removeFirst();
// Check if we unlocked other receives
boolean keepRun = true;
while( keepRun && !queue.isEmpty() ) {
message = queue.peekFirst();
future = getMessageWaiter( message.message().operationName() );
if ( future != null ) { // We found a waiter for the unlocked message
future.setResult( message );
queue.removeFirst();
} else {
keepRun = false;
}
}
}
}
return future;
}
public Future< SessionMessage > requestMessage( InputOperation operation, ExecutionThread ethread )
{
SessionMessageFuture future = new SessionMessageFuture();
ethread.cancelIfKilled( future );
CorrelationSet cset = interpreter().getCorrelationSetForOperation( operation.id() );
synchronized( messageQueues ) {
Deque< SessionMessage > queue;
if ( cset == null ) {
queue = uncorrelatedMessageQueue;
} else {
queue = messageQueues.get( cset );
}
SessionMessage message = queue.peekFirst();
if ( message == null
|| message.message().operationName().equals( operation.id() ) == false
) {
addMessageWaiter( operation, future );
} else {
future.setResult( message );
queue.removeFirst();
// Check if we unlocked other receives
boolean keepRun = true;
SessionMessageFuture currFuture;
while( keepRun && !queue.isEmpty() ) {
message = queue.peekFirst();
currFuture = getMessageWaiter( message.message().operationName() );
if ( currFuture != null ) { // We found a waiter for the unlocked message
currFuture.setResult( message );
queue.removeFirst();
} else {
keepRun = false;
}
}
}
}
return future;
}
private void addMessageWaiter( InputOperation operation, SessionMessageFuture future )
{
Deque< SessionMessageFuture > waitersList = messageWaiters.get( operation.id() );
if ( waitersList == null ) {
waitersList = new LinkedList< SessionMessageFuture >();
messageWaiters.put( operation.id(), waitersList );
}
waitersList.addLast( future );
}
private SessionMessageFuture getMessageWaiter( String operationName )
{
Deque< SessionMessageFuture > waitersList = messageWaiters.get( operationName );
if ( waitersList == null || waitersList.isEmpty() ) {
return null;
}
if ( waitersList.size() == 1 ) {
messageWaiters.remove( operationName );
}
return waitersList.removeFirst();
}
public void pushMessage( SessionMessage message )
{
synchronized( messageQueues ) {
SessionMessageFuture future = getMessageWaiter( message.message().operationName() );
if ( future == null ) {
CorrelationSet cset = interpreter().getCorrelationSetForOperation( message.message().operationName() );
if ( cset != null ) {
messageQueues.get( cset ).addLast( message );
} else {
uncorrelatedMessageQueue.addLast( message );
}
} else {
future.setResult( message );
}
}
}
public void run()
{
try {
try {
process().run();
} catch( ExitingException e ) {}
for( SessionListener listener : listeners ) {
listener.onSessionExecuted( this );
}
} catch( FaultException f ) {
Process p = null;
while( hasScope() && (p = getFaultHandler( f.faultName(), true )) == null ) {
popScope();
}
try {
if ( p == null ) {
Interpreter.getInstance().logUnhandledFault( f );
throw f;
} else {
Value scopeValue =
new VariablePathBuilder( false ).add( currentScopeId(), 0 ).toVariablePath().getValue();
scopeValue.getChildren( f.faultName() ).set( 0, f.value() );
try {
p.run();
} catch( ExitingException e ) {}
}
} catch( FaultException fault ) {
for( SessionListener listener : listeners ) {
listener.onSessionError( this, fault );
}
}
for( SessionListener listener : listeners ) {
listener.onSessionExecuted( this );
}
}
}
public String getSessionId()
{
//return new Long(this.getId()).toString();
return this.getName();
}
}