package org.juxtapose.streamline.util.subscriber;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.juxtapose.streamline.producer.ISTMEntryKey;
import org.juxtapose.streamline.stm.ISTM;
import org.juxtapose.streamline.util.ISTMEntrySubscriber;
import org.juxtapose.streamline.util.ISTMEntry;
/**
* @author Pontus J�rgne
* Feb 2, 2012
* Copyright (c) Pontus J�rgne. All rights reserved
* A data sequencer is used where each data update must be handled in proper order without any misses.
* To deal with race conditions incoming data is only redistributed to the subscriber if sequence number matches the expected sequence numner,
* otherwise the update is put on a queue to wait until all previous updates have been processed.
* This implementation could be extended to use the Disruptor ringbuffer.
*/
public class DataSequencer implements ISTMEntrySubscriber
{
ConcurrentHashMap<Long, Sequence> queue = new ConcurrentHashMap<Long, Sequence>();
AtomicReference<Sequence> polePosition = new AtomicReference<Sequence>(Sequence.INIT_SEQUENCE);
final ISequencedDataSubscriber subscriber;
final ISTM stm;
final ISTMEntryKey key;
final int priority;
/**
* @param inSubscriber
* @param inSTM
* @param inKey
*/
public DataSequencer( ISequencedDataSubscriber inSubscriber, ISTM inSTM, ISTMEntryKey inKey, int inPriority )
{
subscriber = inSubscriber;
stm = inSTM;
key = inKey;
priority = inPriority;
}
public void start()
{
stm.subscribeToData( key, this );
}
public void stop()
{
stm.unsubscribeToData( key, this );
}
/**
* @return
*/
private boolean updatePolePosition()
{
Sequence next = polePosition.get();
if( next.type == Sequence.TYPE_NO_OBJ )
{
Sequence nextSeq = queue.remove( next.id );
if( nextSeq != null )
{
return polePosition.compareAndSet( next, nextSeq );
}
}
return false;
}
private boolean trySequence( Sequence inPoleObj, int inType, Long inID, Sequence inTrySequence )
{
if( inPoleObj.type == inType && inPoleObj.id.equals( inID ))
{
if( polePosition.compareAndSet( inPoleObj, inTrySequence ) )
{
subscriber.dataUpdated( this );
assert polePosition.get().type == Sequence.TYPE_NO_OBJ : "Object has not been remove from pole position";
while( updatePolePosition() )
{
subscriber.dataUpdated( this );
assert polePosition.get().type == Sequence.TYPE_NO_OBJ : "Object has not been remove from pole position";
}
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.util.IDataSubscriber#updateData(java.lang.String, org.juxtapose.streamline.util.IPublishedData, boolean)
*/
public void updateData( ISTMEntryKey inKey, ISTMEntry inData, boolean inFirstUpdate )
{
Sequence syncObj = new Sequence( inData.getSequenceID(), inData, Sequence.TYPE_OBJ);
Sequence poleObj = polePosition.get();
if( trySequence( poleObj, Sequence.TYPE_NO_OBJ, syncObj.id, syncObj ) )
return;
if( trySequence( Sequence.INIT_SEQUENCE, Sequence.TYPE_INIT, Sequence.INIT_SEQUENCE.id, syncObj ) )
return;
else
{
do
{
poleObj = polePosition.get();
queue.remove( syncObj.id );
if( trySequence( poleObj, Sequence.TYPE_NO_OBJ, syncObj.id, syncObj ) )
return;
else if( poleObj.id >= syncObj.id )
{
return;
}
else
{
queue.put( syncObj.id, syncObj );
}
}while( polePosition.get() != poleObj );
}
}
/**
* @return
*/
public ISTMEntry get()
{
Sequence ret = polePosition.get();
Sequence inBetweenSequence = new Sequence( ret.id+1, null, Sequence.TYPE_NO_OBJ );
polePosition.set( inBetweenSequence );
return ret.object;
}
public ISTMEntryKey getDataKey()
{
return key;
}
@Override
public int getPriority()
{
return priority;
}
}