package org.juxtapose.streamline.stm;
import static org.juxtapose.streamline.tools.DataConstants.FIELD_QUERY_KEY;
import static org.juxtapose.streamline.tools.STMAssertionUtil.PRODUCER_SERVICES;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.juxtapose.streamline.producer.ISTMEntryKey;
import org.juxtapose.streamline.producer.ISTMEntryProducer;
import org.juxtapose.streamline.producer.ISTMEntryProducerService;
import org.juxtapose.streamline.producer.executor.IExecutable;
import org.juxtapose.streamline.producer.executor.IExecutor;
import org.juxtapose.streamline.tools.DataConstants;
import org.juxtapose.streamline.tools.KeyConstants;
import org.juxtapose.streamline.util.ISTMEntry;
import org.juxtapose.streamline.util.ISTMEntryRequestSubscriber;
import org.juxtapose.streamline.util.ISTMEntrySubscriber;
import org.juxtapose.streamline.util.ISTMRequestor;
import org.juxtapose.streamline.util.Status;
import org.juxtapose.streamline.util.data.DataType;
import org.juxtapose.streamline.util.data.DataTypeString;
import org.juxtapose.streamline.util.net.ClientConnector;
import org.juxtapose.streamline.util.net.ServerConnector;
import org.juxtapose.streamline.util.producerservices.ProducerServiceConstants;
import com.trifork.clj_ds.IPersistentMap;
import static org.juxtapose.streamline.tools.STMMessageConstants.*;
/**
* @author Pontus J�rgne
* 28 jun 2011
* Copyright (c) Pontus J�rgne. All rights reserved
*
*Software Transactional Memory
*
*/
public abstract class STM implements ISTM, ISTMEntryProducerService, ISTMEntrySubscriber, ISTMEntryProducer
{
protected final ConcurrentHashMap<String, ISTMEntry> keyToData = new ConcurrentHashMap<String, ISTMEntry>();
//Services that create producers to data id is service ID
protected final ConcurrentHashMap<String, ISTMEntryProducerService> idToProducerService = new ConcurrentHashMap<String, ISTMEntryProducerService>();
private IExecutor executor;
private ISTMEntryFactory entryFactory;
private ServerConnector serverConnector;
private ClientConnector clientConnector;
private boolean master;
/**
* @param inExecutor
*/
public void init( IExecutor inExecutor, boolean inMaster )
{
executor = inExecutor;
keyToData.put( KeyConstants.PRODUCER_SERVICE_KEY.getKey(), createEmptyData(Status.OK, this, this));
registerProducer( this, Status.OK );
//TICKET
//Connector should be removed and replaced with some config
master = inMaster;
if( inMaster )
{
serverConnector = new ServerConnector( this, 8085 );
serverConnector.run();
}
else
{
clientConnector = new ClientConnector( "127.0.0.1", 8085, this );
clientConnector.run();
}
}
/**
* @param inProducerService
* @param initState
*/
public void registerProducer( final ISTMEntryProducerService inProducerService, final Status initState )
{
String id = inProducerService.getServiceId();
if( idToProducerService.putIfAbsent( id, inProducerService ) != null )
{
logError( "Producer "+inProducerService.getServiceId()+" already exists" );
return;
}
commit( new STMTransaction( KeyConstants.PRODUCER_SERVICE_KEY, this, 0, 0, false )
{
@Override
public void execute()
{
putValue( inProducerService.getServiceId(), new DataTypeString( initState.toString() ) );
logInfo( "Producer "+inProducerService.getServiceId()+" registered" );
}
});
}
/**
* @param inProducerService
* @param initState
*/
public void updateProducerStatus( final ISTMEntryProducerService inProducerService, final Status newStatus )
{
commit( new STMTransaction( KeyConstants.PRODUCER_SERVICE_KEY, this, 0, 0, false )
{
@Override
public void execute()
{
putValue( inProducerService.getServiceId(), new DataTypeString( newStatus.toString() ) );
}
});
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.util.producer.IDataProducerService#getServiceId()
*/
@Override
public String getServiceId()
{
return ProducerServiceConstants.STM_SERVICE_KEY;
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.util.producer.IDataProducerService#getKey(java.util.HashMap)
*/
@Override
public void getDataKey( ISTMEntryRequestSubscriber inSubscriber, Object inTag, Map<String, String> inQuery)
{
String val = inQuery.get( FIELD_QUERY_KEY );
if( val != null && PRODUCER_SERVICES.equals( val ) )
{
inSubscriber.deliverKey( KeyConstants.PRODUCER_SERVICE_KEY, inTag );
}
else
{
inSubscriber.queryNotAvailible( inTag );
}
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.exp.ISTM#getDataKey(java.lang.Integer, java.util.HashMap)
*/
public void getDataKey(String inProducerService, ISTMEntryRequestSubscriber inSubscriber, Object inTag, Map<String, String> inQuery)
{
ISTMEntryProducerService producerService = idToProducerService.get( inProducerService );
if( producerService == null )
{
logError( "Producer "+inProducerService+" could not be found ");
inSubscriber.queryNotAvailible( inTag );
}
producerService.getDataKey( inSubscriber, inTag, inQuery );
}
@Override
public ISTMEntryProducer getDataProducer(ISTMEntryKey inDataKey)
{
return this;
}
public void updateData( ISTMEntryKey inKey, ISTMEntry inData, boolean inFirstUpdate )
{
}
public void init()
{
}
public void dispose()
{
}
/**
* @param inDataFactory
*/
public void setDataFactory( ISTMEntryFactory inDataFactory )
{
entryFactory = inDataFactory;
}
/**
* @param inStatus
* @param inProducer
* @param inSubscriber
* @return
*/
public ISTMEntry createEmptyData( Status inStatus, ISTMEntryProducer inProducer, ISTMEntrySubscriber inSubscriber )
{
if( entryFactory == null )
{
logError( "Datafactory has not been initiated" );
System.exit(1);
}
return entryFactory.createData(inStatus, inProducer, inSubscriber);
}
/**
* @param inStatus
* @param inProducer
* @return
*/
protected ISTMEntry createEmptyData( Status inStatus, ISTMEntryProducer inProducer )
{
if( entryFactory == null )
{
logError( "Datafactory has not been initiated" );
System.exit(1);
}
return entryFactory.createData(inStatus, inProducer );
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.exp.ISTM#logInfo(java.lang.String)
*/
public void logInfo( String inMessage )
{
System.out.println( inMessage );
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.exp.ISTM#logError(java.lang.String)
*/
public void logError( String inMessage )
{
System.err.println( inMessage );
}
public void logError( String inMessage, Throwable inThrowable )
{
System.err.println( inMessage );
inThrowable.printStackTrace();
}
public void logWarning( String inMessage )
{
System.err.println( inMessage );
}
public void logDebug( String inMessage )
{
System.err.println( inMessage );
}
public ISTMEntry getData( String inKey )
{
return keyToData.get( inKey );
}
@Override
public void execute(IExecutable inExecutable, int inPrio)
{
executor.execute( inExecutable, inPrio );
}
@Override
public void execute(IExecutable inExecutable, int inPrio, String inSequenceKey)
{
executor.execute( inExecutable, inPrio, inSequenceKey );
}
@Override
public void executeBlocking(IExecutable inExecutable, int inPrio, ReentrantLock inSequenceLock)
{
executor.executeBlocking( inExecutable, inPrio, inSequenceLock );
}
@Override
public void scheduleExecution(IExecutable inExecutable, int inPrio, long inTime, TimeUnit inTimeUnit)
{
executor.scheduleExecution( inExecutable, inPrio, inTime, inTimeUnit );
}
public void deliverKey( ISTMEntryKey inDataKey, Long inTag )
{
}
public void queryNotAvailible( Long inTag )
{
}
public void addDependency( String inKey, TemporaryController inController )
{}
public TemporaryController removeDependency( String inDataKey )
{
return null;
}
public void addDataReferences( String inFieldKey, ReferenceLink inLink ){}
public ReferenceLink removeReferenceLink( String inField ){ return null; }
public void disposeReferenceLinks( List< String > inReferenceFields ){}
public void referencedDataUpdated( final String inFieldKey, final ReferenceLink inLink, final ISTMEntry inData ){}
public int getPriority()
{
return IExecutor.LOW;
}
public void setPriority( int inPriority )
{
}
@Override
public boolean isDisposed()
{
return false;
}
@Override
public HashSet<TemporaryController> getDependencyControllers()
{
return null;
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.impl.STM#subscribe(org.juxtapose.streamline.util.producer.IDataKey, org.juxtapose.streamline.util.IDataSubscriber)
*/
public void subscribeToData( ISTMEntryKey inDataKey, ISTMEntrySubscriber inSubscriber )
{
ISTMEntryProducerService producerService = idToProducerService.get( inDataKey.getService() );
if( producerService == null )
{
logError( "Key: "+inDataKey+" not valid, producer service does not exist" );
return;
}
ISTMEntryProducer producer = null;
ISTMEntry newData = null;
int newPriority = -1;
HashSet<TemporaryController> dependencies = null;
lock( inDataKey.getKey() );
try
{
ISTMEntry existingData = keyToData.get( inDataKey.getKey() );
if( existingData == null )
{
//First subscriber
producer = producerService.getDataProducer( inDataKey );
//REVISIT Potentially we should not notify subscribers for certain newDatas and just wait for the initial update instead.
newData = createEmptyData( Status.ON_REQUEST, producer, inSubscriber);
keyToData.put( inDataKey.getKey(), newData );
if( inSubscriber.getPriority() == IExecutor.HIGH )
producer.setPriority( IExecutor.HIGH );
}
else
{
newData = existingData.addSubscriber( inSubscriber );
keyToData.put( inDataKey.getKey(), newData );
if( newData.getPriority() != existingData.getPriority() && existingData.getPriority() != IExecutor.HIGH )
{
newPriority = newData.getPriority();
newData.getProducer().setPriority( newPriority );
dependencies = newData.getProducer().getDependencyControllers();
}
}
}catch( Throwable t )
{
logError( t.getMessage(), t );
}
finally
{
unlock( inDataKey.getKey() );
}
if( dependencies != null )
{
for( TemporaryController tc : dependencies )
{
tc.setPriority( newPriority );
}
}
if( newData != null )
inSubscriber.updateData( inDataKey, newData, true );
if( producer != null )
producer.init();
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.exp.ISTM#unsubscribeToData(org.juxtapose.streamline.producer.IDataKey, org.juxtapose.streamline.util.IDataSubscriber)
*/
@Override
public void unsubscribeToData(ISTMEntryKey inDataKey, ISTMEntrySubscriber inSubscriber)
{
ISTMEntryProducerService producerService = idToProducerService.get( inDataKey.getService() );
if( producerService == null )
{
logError( "Key: "+inDataKey+" not valid, producer service does not exist" );
return;
}
ISTMEntryProducer producer = null;
lock( inDataKey.getKey() );
try
{
ISTMEntry existingData = keyToData.get( inDataKey.getKey() );
if( existingData == null )
{
logError( "Key: "+inDataKey+", Data has already been removed which is unconditional since an existing subscriber is requesting to unsubscribe" );
return;
}
else
{
ISTMEntry newData = existingData.removeSubscriber( inSubscriber );
if( newData.hasSubscribers() )
{
keyToData.replace( inDataKey.getKey(), newData );
}
else
{
keyToData.remove( inDataKey.getKey() );
producer = existingData.getProducer();
}
}
}catch( Throwable t )
{
logError( t.getMessage(), t );
}
finally
{
unlock( inDataKey.getKey() );
}
if( producer != null )
producer.dispose();
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.ISTM#updateSubscriberPriority(org.juxtapose.streamline.producer.ISTMEntryKey, org.juxtapose.streamline.util.ISTMEntrySubscriber)
*/
public void updateSubscriberPriority( ISTMEntryKey inDataKey, ISTMEntrySubscriber inSubscriber )
{
lock( inDataKey.getKey() );
ISTMEntryProducer producer = null;
int newPriority = 0;
HashSet<TemporaryController> dependencies = null;
try
{
ISTMEntry existingData = keyToData.get( inDataKey.getKey() );
if( existingData == null )
{
//All Subscribers has left the building
return;
}
ISTMEntry newEntry = existingData.changeSubscriberPriority( inSubscriber, inSubscriber.getPriority() );
if( newEntry == null )
{
//Subscriber has left the entry
return;
}
if( existingData.getPriority() == newEntry.getPriority() )
{
//No side effects
return;
}
newPriority = newEntry.getPriority();
producer = newEntry.getProducer();
if( producer == null || producer.isDisposed() )
{
//producer is disposed
return;
}
dependencies = producer.getDependencyControllers();
}
catch( Throwable t )
{
logError( t.getMessage(), t );
}
finally
{
unlock( inDataKey.getKey() );
}
if( dependencies != null )
{
for( TemporaryController tc : dependencies )
{
tc.setPriority( newPriority );
}
}
}
/* (non-Javadoc)
* @see org.juxtapose.streamline.stm.ISTM#publish(org.juxtapose.streamline.producer.ISTMEntryKey, org.juxtapose.streamline.producer.ISTMEntryProducer, org.juxtapose.streamline.util.Status, com.trifork.clj_ds.IPersistentMap, java.util.HashSet)
*/
public void publish( ISTMEntryKey inDataKey, ISTMEntryProducer inProducer, Status inStatus, IPersistentMap<String, DataType<?>> inData, HashSet<String> inDeltaSet )
{
lock( inDataKey.getKey() );
try
{
ISTMEntry entry = keyToData.get( inDataKey.getKey() );
if( entry != null )
{
//If data already exists it can only be modified by its own producer
if( ! entry.getProducer().equals( inProducer ) )
{
logError( "Producer "+inProducer+" tried to publish a record for Key: "+inDataKey+" That is owned by producer: "+entry.getProducer() );
return;
}
}
else
{
entry = createEmptyData( inStatus, inProducer );
entry = entry.addSubscriber( inProducer );
entry = entry.setUpdatedData( inData, new HashSet<String>(), true );
}
keyToData.put( inDataKey.getKey(), entry );
}
catch( Throwable t )
{
logError( t.getMessage(), t );
}
finally
{
unlock( inDataKey.getKey() );
}
}
public void request( String inService, int inTag, long inType, ISTMRequestor inRequestor, String inVariable, IPersistentMap<String, DataType<?>> inData )
{
ISTMEntryProducerService producerService = idToProducerService.get( inService );
if( producerService == null )
{
logError( PRODUCER_NOT_EXISTS );
return;
}
producerService.request( inTag, inType, inRequestor, inVariable, inData );
}
public void request( int inTag, long inType, ISTMRequestor inRequestor, String inVariable, IPersistentMap<String, DataType<?>> inData )
{
inRequestor.reply( inTag, DataConstants.RESPONSE_TYPE_ERROR, REQUEST_NOT_SUPPORTED, null );
}
abstract protected void lock( String inKey );
abstract protected void unlock( String inKey );
}