/**
* Copyright 2016 Yahoo Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yahoo.pulsar.client.impl;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import com.yahoo.pulsar.client.api.*;
import org.apache.commons.codec.digest.DigestUtils;
import com.google.common.collect.Queues;
import com.yahoo.pulsar.client.util.FutureUtil;
import com.yahoo.pulsar.common.api.proto.PulsarApi.CommandAck.AckType;
import com.yahoo.pulsar.common.api.proto.PulsarApi.CommandSubscribe.SubType;
import com.yahoo.pulsar.common.util.collections.GrowableArrayBlockingQueue;
public abstract class ConsumerBase extends HandlerBase implements Consumer {
enum ConsumerType {
PARTITIONED, NON_PARTITIONED
}
protected final String subscription;
protected final ConsumerConfiguration conf;
protected final String consumerName;
protected final CompletableFuture<Consumer> subscribeFuture;
protected final MessageListener listener;
protected final ExecutorService listenerExecutor;
final BlockingQueue<Message> incomingMessages;
protected final ConcurrentLinkedQueue<CompletableFuture<Message>> pendingReceives;
protected final int maxReceiverQueueSize;
protected ConsumerBase(PulsarClientImpl client, String topic, String subscription, ConsumerConfiguration conf,
int receiverQueueSize, ExecutorService listenerExecutor, CompletableFuture<Consumer> subscribeFuture) {
super(client, topic);
this.maxReceiverQueueSize = receiverQueueSize;
this.subscription = subscription;
this.conf = conf;
this.consumerName = conf.getConsumerName() == null
? DigestUtils.sha1Hex(UUID.randomUUID().toString()).substring(0, 5) : conf.getConsumerName();
this.subscribeFuture = subscribeFuture;
this.listener = conf.getMessageListener();
if (receiverQueueSize <= 1) {
this.incomingMessages = Queues.newArrayBlockingQueue(1);
} else {
this.incomingMessages = new GrowableArrayBlockingQueue<>();
}
this.listenerExecutor = listenerExecutor;
this.pendingReceives = Queues.newConcurrentLinkedQueue();
}
@Override
public Message receive() throws PulsarClientException {
if (listener != null) {
throw new PulsarClientException.InvalidConfigurationException(
"Cannot use receive() when a listener has been set");
}
switch (getState()) {
case Ready:
case Connecting:
break; // Ok
case Closing:
case Closed:
throw new PulsarClientException.AlreadyClosedException("Consumer already closed");
case Failed:
case Uninitialized:
throw new PulsarClientException.NotConnectedException();
}
return internalReceive();
}
@Override
public CompletableFuture<Message> receiveAsync() {
if (listener != null) {
return FutureUtil.failedFuture(new PulsarClientException.InvalidConfigurationException(
"Cannot use receive() when a listener has been set"));
}
switch (getState()) {
case Ready:
case Connecting:
break; // Ok
case Closing:
case Closed:
return FutureUtil.failedFuture(new PulsarClientException.AlreadyClosedException("Consumer already closed"));
case Failed:
case Uninitialized:
return FutureUtil.failedFuture(new PulsarClientException.NotConnectedException());
}
return internalReceiveAsync();
}
abstract protected Message internalReceive() throws PulsarClientException;
abstract protected CompletableFuture<Message> internalReceiveAsync();
@Override
public Message receive(int timeout, TimeUnit unit) throws PulsarClientException {
if (conf.getReceiverQueueSize() == 0) {
throw new PulsarClientException.InvalidConfigurationException(
"Can't use receive with timeout, if the queue size is 0");
}
if (listener != null) {
throw new PulsarClientException.InvalidConfigurationException(
"Cannot use receive() when a listener has been set");
}
switch (getState()) {
case Ready:
case Connecting:
break; // Ok
case Closing:
case Closed:
throw new PulsarClientException.AlreadyClosedException("Consumer already closed");
case Failed:
case Uninitialized:
throw new PulsarClientException.NotConnectedException();
}
return internalReceive(timeout, unit);
}
abstract protected Message internalReceive(int timeout, TimeUnit unit) throws PulsarClientException;
@Override
public void acknowledge(Message message) throws PulsarClientException {
try {
acknowledge(message.getMessageId());
} catch (NullPointerException npe) {
throw new PulsarClientException.InvalidMessageException(npe.getMessage());
}
}
@Override
public void acknowledge(MessageId messageId) throws PulsarClientException {
try {
acknowledgeAsync(messageId).get();
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof PulsarClientException) {
throw (PulsarClientException) t;
} else {
throw new PulsarClientException(t);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PulsarClientException(e);
}
}
@Override
public void acknowledgeCumulative(Message message) throws PulsarClientException {
try {
acknowledgeCumulative(message.getMessageId());
} catch (NullPointerException npe) {
throw new PulsarClientException.InvalidMessageException(npe.getMessage());
}
}
@Override
public void acknowledgeCumulative(MessageId messageId) throws PulsarClientException {
try {
acknowledgeCumulativeAsync(messageId).get();
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof PulsarClientException) {
throw (PulsarClientException) t;
} else {
throw new PulsarClientException(t);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PulsarClientException(e);
}
}
@Override
public CompletableFuture<Void> acknowledgeAsync(Message message) {
try {
return acknowledgeAsync(message.getMessageId());
} catch (NullPointerException npe) {
return FutureUtil.failedFuture(new PulsarClientException.InvalidMessageException(npe.getMessage()));
}
}
@Override
public CompletableFuture<Void> acknowledgeCumulativeAsync(Message message) {
try {
return acknowledgeCumulativeAsync(message.getMessageId());
} catch (NullPointerException npe) {
return FutureUtil.failedFuture(new PulsarClientException.InvalidMessageException(npe.getMessage()));
}
}
@Override
public CompletableFuture<Void> acknowledgeAsync(MessageId messageId) {
return doAcknowledge(messageId, AckType.Individual);
}
@Override
public CompletableFuture<Void> acknowledgeCumulativeAsync(MessageId messageId) {
if (conf.getSubscriptionType() != SubscriptionType.Exclusive) {
return FutureUtil.failedFuture(new PulsarClientException.InvalidConfigurationException(
"Cannot use cumulative acks on a non-exclusive subscription"));
}
return doAcknowledge(messageId, AckType.Cumulative);
}
abstract protected CompletableFuture<Void> doAcknowledge(MessageId messageId, AckType ackType);
@Override
public void unsubscribe() throws PulsarClientException {
try {
unsubscribeAsync().get();
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof PulsarClientException) {
throw (PulsarClientException) t;
} else {
throw new PulsarClientException(t);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PulsarClientException(e);
}
}
@Override
abstract public CompletableFuture<Void> unsubscribeAsync();
@Override
public void close() throws PulsarClientException {
try {
closeAsync().get();
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof PulsarClientException) {
throw (PulsarClientException) t;
} else {
throw new PulsarClientException(t);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PulsarClientException(e);
}
}
@Override
abstract public CompletableFuture<Void> closeAsync();
protected SubType getSubType() {
SubscriptionType type = conf.getSubscriptionType();
switch (type) {
case Exclusive:
return SubType.Exclusive;
case Shared:
return SubType.Shared;
case Failover:
return SubType.Failover;
}
// Should not happen since we cover all cases above
return null;
}
abstract public boolean isConnected();
abstract public int getAvailablePermits();
abstract public int numMessagesInQueue();
public CompletableFuture<Consumer> subscribeFuture() {
return subscribeFuture;
}
@Override
public String getTopic() {
return topic;
}
@Override
public String getSubscription() {
return subscription;
}
/**
* Redelivers the given unacknowledged messages. In Failover mode, the request is ignored if the consumer is not
* active for the given topic. In Shared mode, the consumers messages to be redelivered are distributed across all
* the connected consumers. This is a non blocking call and doesn't throw an exception. In case the connection
* breaks, the messages are redelivered after reconnect.
*/
protected abstract void redeliverUnacknowledgedMessages(Set<MessageIdImpl> messageIds);
}