package ee.telekom.workflow.executor.queue;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IQueue;
import com.hazelcast.core.ItemEvent;
import com.hazelcast.core.ItemListener;
import ee.telekom.workflow.core.common.WorkflowEngineConfiguration;
import ee.telekom.workflow.core.workunit.WorkUnit;
@Component
public class HazelcastWorkQueue implements WorkQueue{
private static final Logger log = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
private static final String WORK_QUEUE_NAME = "work";
@Autowired
private WorkflowEngineConfiguration config;
private HazelcastInstance hcInstance;
private AtomicBoolean isLocalHcInstance = new AtomicBoolean( false );
private AtomicBoolean isStarted = new AtomicBoolean( false );
@Override
public void start(){
String hcInstanceName = config.getClusterHazelcastName();
hcInstance = Hazelcast.getHazelcastInstanceByName( hcInstanceName );
if (hcInstance == null) {
log.info( "Didn't find an existing Hazelcast instance by name " + hcInstanceName + ". Starting our own instance!" );
Config hcConfig = new Config();
// Unfortunately, using the following line does not yet apply during the Hazelcast
// initialization such that Hazelcast initalization log output ends up at stout.
// This propblem is resolved by setting a system property as shown below.
// hcConfig.setProperty( "hazelcast.logging.type", "slf4j" );
System.setProperty( "hazelcast.logging.class", "com.hazelcast.logging.Slf4jFactory" );
hcConfig.setProperty( "hazelcast.jmx", "true" );
hcConfig.setProperty( "hazelcast.shutdownhook.enabled", "false" );
hcConfig.setInstanceName( hcInstanceName );
hcConfig.getGroupConfig().setName( config.getClusterName() );
hcConfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled( true );
hcConfig.getNetworkConfig().getJoin().getMulticastConfig().setMulticastGroup( config.getClusterMulticastGroup() );
hcConfig.getNetworkConfig().getJoin().getMulticastConfig().setMulticastPort( config.getClusterMulticastPort() );
hcConfig.getNetworkConfig().getJoin().getMulticastConfig().setMulticastTimeToLive( config.getClusterMulticastTtl() );
hcInstance = Hazelcast.newHazelcastInstance( hcConfig );
isLocalHcInstance.set( true );
} else {
log.info( "Found an existing Hazelcast instance by name " + hcInstanceName + ". Using that." );
}
isStarted.set( true );
log.info( "Started queue" );
}
@Override
public void stop(){
log.debug( "Stopping queue" );
if (isLocalHcInstance.get()) {
hcInstance.getLifecycleService().shutdown();
}
isStarted.set( false );
log.info( "Stopped queue" );
}
@Override
public boolean isStarted(){
return isStarted.get();
}
private IQueue<WorkUnit> getWorkQueue(){
return hcInstance.getQueue( WORK_QUEUE_NAME );
}
@Override
public void put( WorkUnit workUnit ) throws InterruptedException{
getWorkQueue().put( workUnit );
}
@Override
public WorkUnit poll( long timeout, TimeUnit unit ) throws InterruptedException{
return getWorkQueue().poll( timeout, unit );
}
@Override
public void awaitEmpty(){
IsEmptyListener listener = new IsEmptyListener( getWorkQueue() );
boolean includeItemValuesInNotificationEvents = false;
String registrationId = getWorkQueue().addItemListener( listener, includeItemValuesInNotificationEvents );
while( !getWorkQueue().isEmpty() ){
try{
listener.awaitEmpty( getWorkQueue() );
}
catch( InterruptedException ignore ){
}
}
getWorkQueue().removeItemListener( registrationId );
}
private static class IsEmptyListener implements ItemListener<WorkUnit>{
private final Object monitor = new Object();
private final IQueue<WorkUnit> queue;
public IsEmptyListener( IQueue<WorkUnit> queue ){
this.queue = queue;
}
@Override
public void itemAdded( ItemEvent<WorkUnit> event ){
}
@Override
public void itemRemoved( ItemEvent<WorkUnit> e ){
if( queue.isEmpty() ){
synchronized( monitor ){
monitor.notifyAll();
}
}
}
public void awaitEmpty( IQueue<?> queue ) throws InterruptedException{
synchronized( monitor ){
if( !queue.isEmpty() ){
monitor.wait();
}
}
}
}
}