package com.leansoft.luxun.producer;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import com.leansoft.luxun.broker.Broker;
import com.leansoft.luxun.broker.BrokerInfo;
import com.leansoft.luxun.broker.ConfigBrokerInfo;
import com.leansoft.luxun.common.annotations.ClientSide;
import com.leansoft.luxun.common.exception.InvalidPartitionException;
import com.leansoft.luxun.common.exception.NoBrokersForTopicException;
import com.leansoft.luxun.producer.async.CallbackHandler;
import com.leansoft.luxun.producer.async.EventHandler;
import com.leansoft.luxun.serializer.Encoder;
import com.leansoft.luxun.utils.Utils;
import static com.leansoft.luxun.utils.Closer.closeQuietly;
/**
* Message producer
*
* @author bulldog
*
* @param <K> partitioner key
* @param <V> real data
*/
@ClientSide
public class Producer<K, V> implements IProducer<K, V> {
ProducerConfig config;
private IPartitioner<K> partitioner;
ProducerPool<V> producerPool;
boolean populateProducerPool;
BrokerInfo brokerInfo;
private final AtomicBoolean hasShutdown = new AtomicBoolean(false);
private final Random random = new Random();
private Encoder<V> encoder;
public Producer(ProducerConfig config, IPartitioner<K> partitioner, ProducerPool<V> producerPool, boolean populateProducerPool,
BrokerInfo brokerInfo) {
super();
this.config = config;
this.partitioner = partitioner;
this.producerPool = producerPool;
if(this.producerPool == null) {
this.producerPool = new ProducerPool<V>(config, getEncoder());
}
this.populateProducerPool = populateProducerPool;
this.brokerInfo = brokerInfo;
if (this.brokerInfo == null) {
this.brokerInfo = new ConfigBrokerInfo(config.getBrokerList());
}
// pool of producers, one per broker
if (this.populateProducerPool) {
List<Integer> brokerIdList = this.brokerInfo.getBrokerIdList();
for(Integer brokerId : brokerIdList) {
Broker b = this.brokerInfo.getBrokerInfo(brokerId);
this.producerPool.addProducerForBroker(new Broker(brokerId, b.host, b.host, b.port));
}
}
}
/**
* This constructor can be used when all config parameters will be
* specified through the ProducerConfig object
*
* @param config Producer Configuration object
*/
public Producer(ProducerConfig config) {
this(config, //
null, //
null, //
true, //
null);
}
/**
* This constructor can be used to provide pre-instantiated objects for
* all config parameters that would otherwise be instantiated via
* reflection. i.e. encoder, event handler and callback
* handler. If you use this constructor, encoder, eventHandler,
* and callback handler will not be picked up from the
* config.
*
* @param config Producer Configuration object
* @param encoder Encoder used to convert an object of type V to
* binary data. If this is null it throws an
* InvalidConfigException
* @param eventHandler the class that implements
* luxun.producer.async.EventHandler<T> used to dispatch a
* batch of produce requests, using an instance of
* luxun.producer.SyncProducer. If this is null, it uses the
* DefaultEventHandler
* @param cbkHandler the class that implements
* luxun.producer.async.CallbackHandler<T> used to inject
* callbacks at various stages of the
* luxun.producer.AsyncProducer pipeline. If this is null, the
* producer does not use the callback handler and hence does not
* invoke any callbacks
* @param partitioner class that implements the
* luxun.producer.Partitioner<K>, used to supply a custom
* partitioning strategy on the message key (of type K) that is
* specified through the ProducerData<K, T> object in the send
* API. If this is null, producer uses DefaultPartitioner
*/
public Producer(ProducerConfig config, Encoder<V> encoder, EventHandler<V> eventHandler, CallbackHandler<V> cbkHandler, IPartitioner<K> partitioner) {
this(config, //
partitioner,
new ProducerPool<V>(config, encoder, eventHandler, cbkHandler), //
true, //
null);
}
@SuppressWarnings("unchecked")
@Override
public Encoder<V> getEncoder() {
return encoder == null ?(Encoder<V>) Utils.getObject(config.getSerializerClass()):encoder;
}
public void send(ProducerData<K, V> data) throws NoBrokersForTopicException {
if (data == null) return;
configSend(data);
}
private void configSend(ProducerData<K, V> data) {
producerPool.send(create(data));
}
private ProducerPoolData<V> create(ProducerData<K, V> pd) {
List<Integer> brokerIdList = brokerInfo.getBrokerIdList();
if (brokerIdList == null || brokerIdList.size() == 0) {
throw new NoBrokersForTopicException("Topic= " + pd.getTopic());
}
int numBrokers = brokerIdList.size();
int index = pd.getKey() == null ?
random.nextInt(numBrokers) : this.getPartitioner().partition(pd.getKey(), numBrokers);
if (index < 0 || index >= numBrokers) {
throw new InvalidPartitionException("Invalid broker index : " + index + "\n Valid values are in the range inclusive [0, " + (numBrokers - 1)
+ "]");
}
int brokerId = brokerIdList.get(index);
return this.producerPool.buildProducerPoolData(pd.getTopic(),//
brokerInfo.getBrokerInfo(brokerId), pd.getData());
}
@Override
public void close() {
if (hasShutdown.compareAndSet(false, true)) {
closeQuietly(producerPool);
closeQuietly(brokerInfo);
}
}
@SuppressWarnings("unchecked")
@Override
public IPartitioner<K> getPartitioner() {
if(partitioner == null) {
partitioner = (IPartitioner<K>) Utils.getObject(config.getPartitionerClass());
}
return partitioner;
}
}