/*************************************************************************** * 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.process; import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import jolie.ExecutionThread; import jolie.Interpreter; import jolie.lang.Constants; import jolie.monitoring.events.OperationEndedEvent; import jolie.monitoring.events.OperationStartedEvent; import jolie.net.CommChannel; import jolie.net.CommMessage; import jolie.net.SessionMessage; import jolie.runtime.*; import jolie.runtime.expression.Expression; import jolie.runtime.typing.Type; import jolie.runtime.typing.TypeCheckingException; public class RequestResponseProcess implements InputOperationProcess { private final RequestResponseOperation operation; private final VariablePath inputVarPath; // may be null private final Expression outputExpression; // may be null private final Process process; private boolean isSessionStarter = false; public RequestResponseProcess( RequestResponseOperation operation, VariablePath inputVarPath, Expression outputExpression, Process process ) { this.operation = operation; this.inputVarPath = inputVarPath; this.process = process; this.outputExpression = outputExpression; } public void setSessionStarter( boolean isSessionStarter ) { this.isSessionStarter = isSessionStarter; } public InputOperation inputOperation() { return operation; } private void log( String message ) { if ( Interpreter.getInstance().verbose() ) { Interpreter.getInstance().logInfo( "[RequestResponse operation " + operation.id() + "]: " + message ); } } public boolean isKillable() { return true; } public Process clone( TransformationReason reason ) { return new RequestResponseProcess( operation, ( inputVarPath == null ) ? null : (VariablePath)inputVarPath.cloneExpression( reason ), ( outputExpression == null ) ? null : (VariablePath)outputExpression.cloneExpression( reason ), process.clone( reason ) ); } public Process receiveMessage( final SessionMessage sessionMessage, jolie.State state ) { if ( Interpreter.getInstance().isMonitoring() && !isSessionStarter ) { Interpreter.getInstance().fireMonitorEvent( new OperationStartedEvent( operation.id(), ExecutionThread.currentThread().getSessionId() ) ); } log( "received message " + sessionMessage.message().id() ); if ( inputVarPath != null ) { inputVarPath.getValue( state.root() ).refCopy( sessionMessage.message().value() ); } return new Process() { public void run() throws FaultException, ExitingException { runBehaviour( sessionMessage.channel(), sessionMessage.message() ); } public Process clone( TransformationReason reason ) { return this; } public boolean isKillable() { return false; } }; } public void run() throws FaultException, ExitingException { ExecutionThread ethread = ExecutionThread.currentThread(); if ( ethread.isKilled() ) { return; } Future< SessionMessage > f = ethread.requestMessage( operation, ethread ); try { SessionMessage m = f.get(); if ( m != null ) { // If it is null, we got killed by a fault receiveMessage( m, ethread.state() ).run(); } } catch( FaultException e ) { throw e; } catch( ExitingException e ) { throw e; } catch( Exception e ) { Interpreter.getInstance().logSevere( e ); } } public VariablePath inputVarPath() { return inputVarPath; } private CommMessage createFaultMessage( CommMessage request, FaultException f ) throws TypeCheckingException { if ( operation.typeDescription().faults().containsKey( f.faultName() ) ) { Type faultType = operation.typeDescription().faults().get( f.faultName() ); if ( faultType != null ) { faultType.check( f.value() ); } } else { Interpreter.getInstance().logSevere( "Request-Response process for " + operation.id() + " threw an undeclared fault for that operation (" + f.faultName() + "), throwing TypeMismatch" ); f = new FaultException( Constants.TYPE_MISMATCH_FAULT_NAME, "Internal server error" ); } return CommMessage.createFaultResponse( request, f ); } private void runBehaviour( CommChannel channel, CommMessage message ) throws FaultException { // Variables for monitor int responseStatus; String details; FaultException typeMismatch = null; FaultException fault = null; CommMessage response; try { try { process.run(); } catch( ExitingException e ) {} ExecutionThread ethread = ExecutionThread.currentThread(); if ( ethread.isKilled() ) { try { response = createFaultMessage( message, ethread.killerFault() ); responseStatus = OperationEndedEvent.FAULT; details = ethread.killerFault().faultName(); } catch( TypeCheckingException e ) { typeMismatch = new FaultException( Constants.TYPE_MISMATCH_FAULT_NAME, "Request-Response process TypeMismatch for fault " + ethread.killerFault().faultName() + " (operation " + operation.id() + "): " + e.getMessage() ); response = CommMessage.createFaultResponse( message, typeMismatch ); responseStatus = OperationEndedEvent.ERROR; details = typeMismatch.faultName(); } } else { response = CommMessage.createResponse( message, ( outputExpression == null ) ? Value.UNDEFINED_VALUE : outputExpression.evaluate() ); responseStatus = OperationEndedEvent.SUCCESS; details = ""; if ( operation.typeDescription().responseType() != null ) { try { operation.typeDescription().responseType().check( response.value() ); } catch( TypeCheckingException e ) { typeMismatch = new FaultException( Constants.TYPE_MISMATCH_FAULT_NAME, "Request-Response input operation output value TypeMismatch (operation " + operation.id() + "): " + e.getMessage() ); response = CommMessage.createFaultResponse( message, new FaultException( Constants.TYPE_MISMATCH_FAULT_NAME, "Internal server error (TypeMismatch)" ) ); responseStatus = OperationEndedEvent.ERROR; details = Constants.TYPE_MISMATCH_FAULT_NAME; } } } } catch( FaultException f ) { try { response = createFaultMessage( message, f ); responseStatus = OperationEndedEvent.FAULT; details = f.faultName(); } catch( TypeCheckingException e ) { typeMismatch = new FaultException( Constants.TYPE_MISMATCH_FAULT_NAME, "Request-Response process TypeMismatch for fault " + f.faultName() + " (operation " + operation.id() + "): " + e.getMessage() ); response = CommMessage.createFaultResponse( message, typeMismatch ); responseStatus = OperationEndedEvent.ERROR; details = typeMismatch.faultName(); } fault = f; } try { channel.send( response ); log( "sent response for message " + message.id() ); if ( Interpreter.getInstance().isMonitoring() ) { Interpreter.getInstance().fireMonitorEvent( new OperationEndedEvent( operation.id(), ExecutionThread.currentThread().getSessionId(), responseStatus, details)); } } catch( IOException e ) { //Interpreter.getInstance().logSevere( e ); throw new FaultException( Constants.IO_EXCEPTION_FAULT_NAME, e ); } finally { try { channel.release(); // TODO: what if the channel is in disposeForInput? } catch( IOException e ) { Interpreter.getInstance().logSevere( e ); } } if ( fault != null ) { if ( typeMismatch != null ) { Interpreter.getInstance().logWarning( typeMismatch.value().strValue() ); } throw fault; } else if ( typeMismatch != null ) { throw typeMismatch; } } }