/**
* Copyright (c) 2002-2012 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.cluster;
import static org.neo4j.cluster.com.message.Message.CONVERSATION_ID;
import static org.neo4j.cluster.com.message.Message.CREATED_BY;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.neo4j.cluster.com.message.Message;
import org.neo4j.cluster.com.message.MessageProcessor;
import org.neo4j.cluster.com.message.MessageSource;
import org.neo4j.cluster.com.message.MessageType;
import org.neo4j.cluster.statemachine.StateMachine;
import org.neo4j.cluster.statemachine.StateTransitionListener;
import org.neo4j.cluster.timeout.TimeoutStrategy;
import org.neo4j.cluster.timeout.Timeouts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Combines a set of state machines into one. This will
* typically receive messages from the network and then delegate
* to the correct state machine based on what type of message comes in.
* Only one message at a time can be processed.
*/
public class ConnectedStateMachines
implements MessageProcessor, MessageSource
{
private final Logger logger = LoggerFactory.getLogger( ConnectedStateMachines.class );
private final MessageProcessor sender;
private DelayedDirectExecutor executor;
private Timeouts timeouts;
private final Map<Class<? extends MessageType>, StateMachine> stateMachines = new LinkedHashMap<Class<? extends
MessageType>, StateMachine>();
private final List<MessageProcessor> outgoingProcessors = new ArrayList<MessageProcessor>();
private final OutgoingMessageProcessor outgoing;
public ConnectedStateMachines( MessageSource source,
final MessageProcessor sender,
TimeoutStrategy timeoutStrategy,
DelayedDirectExecutor executor )
{
this.sender = sender;
this.executor = executor;
this.timeouts = new Timeouts( this, timeoutStrategy );
outgoing = new OutgoingMessageProcessor();
source.addMessageProcessor( this );
}
public Timeouts getTimeouts()
{
return timeouts;
}
public synchronized void addStateMachine( StateMachine stateMachine )
{
stateMachines.put( stateMachine.getMessageType(), stateMachine );
}
public synchronized void removeStateMachine( StateMachine stateMachine )
{
stateMachines.remove( stateMachine.getMessageType() );
}
public Iterable<StateMachine> getStateMachines()
{
return stateMachines.values();
}
@Override
public void addMessageProcessor( MessageProcessor messageProcessor )
{
outgoingProcessors.add( messageProcessor );
}
public OutgoingMessageProcessor getOutgoing()
{
return outgoing;
}
@Override
public synchronized void process( Message<? extends MessageType> message )
{
// Lock timeouts while we are processing the message
synchronized ( timeouts )
{
StateMachine stateMachine = stateMachines.get( message.getMessageType().getClass() );
if ( stateMachine == null )
{
return; // No StateMachine registered for this MessageType type - Ignore this
}
stateMachine.handle( message, outgoing );
// Process and send messages
// Allow state machines to send messages to each other as well in this loop
Message<? extends MessageType> outgoingMessage;
try
{
while ( (outgoingMessage = outgoing.nextOutgoingMessage()) != null )
{
message.copyHeadersTo( outgoingMessage, CONVERSATION_ID, CREATED_BY );
for ( MessageProcessor outgoingProcessor : outgoingProcessors )
{
try
{
outgoingProcessor.process( outgoingMessage );
}
catch ( Throwable e )
{
logger.warn( "Outgoing message processor threw exception", e );
}
}
if ( outgoingMessage.hasHeader( Message.TO ) )
{
try
{
sender.process( outgoingMessage );
}
catch ( Throwable e )
{
logger.warn( "Message sending threw exception", e );
}
}
else
{
// Deliver internally if possible
StateMachine internalStatemachine = stateMachines.get( outgoingMessage.getMessageType()
.getClass() );
// if (internalStatemachine != null && stateMachine != internalStatemachine )
if ( internalStatemachine != null )
{
internalStatemachine.handle( (Message) outgoingMessage, outgoing );
}
}
}
}
catch ( Exception e )
{
logger.warn( "Error processing message " + message, e );
}
}
// Before returning, process delayed executions so that they are done before returning
// This will effectively trigger all notifications created by contexts
executor.drain();
}
public void addStateTransitionListener( StateTransitionListener stateTransitionListener )
{
for ( StateMachine stateMachine : stateMachines.values() )
{
stateMachine.addStateTransitionListener( stateTransitionListener );
}
}
public void removeStateTransitionListener( StateTransitionListener stateTransitionListener )
{
for ( StateMachine stateMachine : stateMachines.values() )
{
stateMachine.removeStateTransitionListener( stateTransitionListener );
}
}
@Override
public String toString()
{
List<String> states = new ArrayList<String>();
for ( StateMachine stateMachine : stateMachines.values() )
{
states.add( stateMachine.getState().getClass().getSuperclass().getSimpleName() + ":" + stateMachine
.getState().toString() );
}
return states.toString();
}
public StateMachine getStateMachine( Class<? extends MessageType> messageType )
{
return stateMachines.get( messageType );
}
private class OutgoingMessageProcessor
implements MessageProcessor
{
private Queue<Message<? extends MessageType>> outgoingMessages = new LinkedList<Message<? extends
MessageType>>();
@Override
public synchronized void process( Message<? extends MessageType> message )
{
outgoingMessages.offer( message );
}
public synchronized Message<? extends MessageType> nextOutgoingMessage()
{
return outgoingMessages.poll();
}
}
}