Package org.apache.hedwig.client.netty

Source Code of org.apache.hedwig.client.netty.HedwigSubscriber

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.apache.hedwig.client.netty;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;

import com.google.protobuf.ByteString;
import org.apache.hedwig.client.api.MessageHandler;
import org.apache.hedwig.client.api.Subscriber;
import org.apache.hedwig.client.conf.ClientConfiguration;
import org.apache.hedwig.client.data.PubSubData;
import org.apache.hedwig.client.data.TopicSubscriber;
import org.apache.hedwig.client.exceptions.InvalidSubscriberIdException;
import org.apache.hedwig.client.handlers.PubSubCallback;
import org.apache.hedwig.exceptions.PubSubException;
import org.apache.hedwig.exceptions.PubSubException.ClientAlreadySubscribedException;
import org.apache.hedwig.exceptions.PubSubException.ClientNotSubscribedException;
import org.apache.hedwig.exceptions.PubSubException.CouldNotConnectException;
import org.apache.hedwig.exceptions.PubSubException.ServiceDownException;
import org.apache.hedwig.protocol.PubSubProtocol.ConsumeRequest;
import org.apache.hedwig.protocol.PubSubProtocol.MessageSeqId;
import org.apache.hedwig.protocol.PubSubProtocol.OperationType;
import org.apache.hedwig.protocol.PubSubProtocol.ProtocolVersion;
import org.apache.hedwig.protocol.PubSubProtocol.PubSubRequest;
import org.apache.hedwig.protocol.PubSubProtocol.SubscribeRequest;
import org.apache.hedwig.protocol.PubSubProtocol.UnsubscribeRequest;
import org.apache.hedwig.protocol.PubSubProtocol.SubscribeRequest.CreateOrAttach;
import org.apache.hedwig.protoextensions.SubscriptionStateUtils;
import org.apache.hedwig.util.Callback;

/**
* This is the Hedwig Netty specific implementation of the Subscriber interface.
*
*/
public class HedwigSubscriber implements Subscriber {

    private static Logger logger = LoggerFactory.getLogger(HedwigSubscriber.class);

    // Concurrent Map to store the cached Channel connections on the client side
    // to a server host for a given Topic + SubscriberId combination. For each
    // TopicSubscriber, we want a unique Channel connection to the server for
    // it. We can also get the ResponseHandler tied to the Channel via the
    // Channel Pipeline.
    protected final ConcurrentMap<TopicSubscriber, Channel> topicSubscriber2Channel = new ConcurrentHashMap<TopicSubscriber, Channel>();

    protected final HedwigClientImpl client;
    protected final ClientConfiguration cfg;

    public HedwigSubscriber(HedwigClientImpl client) {
        this.client = client;
        this.cfg = client.getConfiguration();
    }

    // Private method that holds the common logic for doing synchronous
    // Subscribe or Unsubscribe requests. This is for code reuse since these
    // two flows are very similar. The assumption is that the input
    // OperationType is either SUBSCRIBE or UNSUBSCRIBE.
    private void subUnsub(ByteString topic, ByteString subscriberId, OperationType operationType,
                          CreateOrAttach createOrAttach) throws CouldNotConnectException, ClientAlreadySubscribedException,
        ClientNotSubscribedException, ServiceDownException {
        if (logger.isDebugEnabled())
            logger.debug("Calling a sync subUnsub request for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8() + ", operationType: " + operationType + ", createOrAttach: "
                         + createOrAttach);
        PubSubData pubSubData = new PubSubData(topic, null, subscriberId, operationType, createOrAttach, null, null);
        synchronized (pubSubData) {
            PubSubCallback pubSubCallback = new PubSubCallback(pubSubData);
            asyncSubUnsub(topic, subscriberId, pubSubCallback, null, operationType, createOrAttach);
            try {
                while (!pubSubData.isDone)
                    pubSubData.wait();
            } catch (InterruptedException e) {
                throw new ServiceDownException("Interrupted Exception while waiting for async subUnsub call");
            }
            // Check from the PubSubCallback if it was successful or not.
            if (!pubSubCallback.getIsCallSuccessful()) {
                // See what the exception was that was thrown when the operation
                // failed.
                PubSubException failureException = pubSubCallback.getFailureException();
                if (failureException == null) {
                    // This should not happen as the operation failed but a null
                    // PubSubException was passed. Log a warning message but
                    // throw a generic ServiceDownException.
                    logger.error("Sync SubUnsub operation failed but no PubSubException was passed!");
                    throw new ServiceDownException("Server ack response to SubUnsub request is not successful");
                }
                // For the expected exceptions that could occur, just rethrow
                // them.
                else if (failureException instanceof CouldNotConnectException)
                    throw (CouldNotConnectException) failureException;
                else if (failureException instanceof ClientAlreadySubscribedException)
                    throw (ClientAlreadySubscribedException) failureException;
                else if (failureException instanceof ClientNotSubscribedException)
                    throw (ClientNotSubscribedException) failureException;
                else if (failureException instanceof ServiceDownException)
                    throw (ServiceDownException) failureException;
                else {
                    logger.error("Unexpected PubSubException thrown: " + failureException.toString());
                    // Throw a generic ServiceDownException but wrap the
                    // original PubSubException within it.
                    throw new ServiceDownException(failureException);
                }
            }
        }
    }

    // Private method that holds the common logic for doing asynchronous
    // Subscribe or Unsubscribe requests. This is for code reuse since these two
    // flows are very similar. The assumption is that the input OperationType is
    // either SUBSCRIBE or UNSUBSCRIBE.
    private void asyncSubUnsub(ByteString topic, ByteString subscriberId, Callback<Void> callback, Object context,
                               OperationType operationType, CreateOrAttach createOrAttach) {
        if (logger.isDebugEnabled())
            logger.debug("Calling an async subUnsub request for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8() + ", operationType: " + operationType + ", createOrAttach: "
                         + createOrAttach);
        // Check if we know which server host is the master for the topic we are
        // subscribing to.
        PubSubData pubSubData = new PubSubData(topic, null, subscriberId, operationType, createOrAttach, callback,
                                               context);
        if (client.topic2Host.containsKey(topic)) {
            InetSocketAddress host = client.topic2Host.get(topic);
            if (operationType.equals(OperationType.UNSUBSCRIBE) && client.getPublisher().host2Channel.containsKey(host)) {
                // For unsubscribes, we can reuse the channel connections to the
                // server host that are cached for publishes. For publish and
                // unsubscribe flows, we will thus use the same Channels and
                // will cache and store them during the ConnectCallback.
                doSubUnsub(pubSubData, client.getPublisher().host2Channel.get(host));
            } else {
                // We know which server host is the master for the topic so
                // connect to that first. For subscribes, we want a new channel
                // connection each time for the TopicSubscriber. If the
                // TopicSubscriber is already connected and subscribed,
                // we assume the server will respond with an appropriate status
                // indicating this. For unsubscribes, it is possible that the
                // client is subscribed to the topic already but does not
                // have a Channel connection yet to the server host. e.g. Client
                // goes down and comes back up but client side soft state memory
                // does not have the netty Channel connection anymore.
                client.doConnect(pubSubData, host);
            }
        } else {
            // Server host for the given topic is not known yet so use the
            // default server host/port as defined in the configs. This should
            // point to the server VIP which would redirect to a random server
            // (which might not be the server hosting the topic).
            client.doConnect(pubSubData, cfg.getDefaultServerHost());
        }
    }

    public void subscribe(ByteString topic, ByteString subscriberId, CreateOrAttach mode)
            throws CouldNotConnectException, ClientAlreadySubscribedException, ServiceDownException,
        InvalidSubscriberIdException {
        subscribe(topic, subscriberId, mode, false);
    }

    protected void subscribe(ByteString topic, ByteString subscriberId, CreateOrAttach mode, boolean isHub)
            throws CouldNotConnectException, ClientAlreadySubscribedException, ServiceDownException,
        InvalidSubscriberIdException {
        // Validate that the format of the subscriberId is valid either as a
        // local or hub subscriber.
        if (!isValidSubscriberId(subscriberId, isHub)) {
            throw new InvalidSubscriberIdException("SubscriberId passed is not valid: " + subscriberId.toStringUtf8()
                                                   + ", isHub: " + isHub);
        }
        try {
            subUnsub(topic, subscriberId, OperationType.SUBSCRIBE, mode);
        } catch (ClientNotSubscribedException e) {
            logger.error("Unexpected Exception thrown: " + e.toString());
            // This exception should never be thrown here. But just in case,
            // throw a generic ServiceDownException but wrap the original
            // Exception within it.
            throw new ServiceDownException(e);
        }
    }

    public void asyncSubscribe(ByteString topic, ByteString subscriberId, CreateOrAttach mode, Callback<Void> callback,
                               Object context) {
        asyncSubscribe(topic, subscriberId, mode, callback, context, false);
    }

    protected void asyncSubscribe(ByteString topic, ByteString subscriberId, CreateOrAttach mode,
                                  Callback<Void> callback, Object context, boolean isHub) {
        // Validate that the format of the subscriberId is valid either as a
        // local or hub subscriber.
        if (!isValidSubscriberId(subscriberId, isHub)) {
            callback.operationFailed(context, new ServiceDownException(new InvalidSubscriberIdException(
                                         "SubscriberId passed is not valid: " + subscriberId.toStringUtf8() + ", isHub: " + isHub)));
            return;
        }
        asyncSubUnsub(topic, subscriberId, callback, context, OperationType.SUBSCRIBE, mode);
    }

    public void unsubscribe(ByteString topic, ByteString subscriberId) throws CouldNotConnectException,
        ClientNotSubscribedException, ServiceDownException, InvalidSubscriberIdException {
        unsubscribe(topic, subscriberId, false);
    }

    protected void unsubscribe(ByteString topic, ByteString subscriberId, boolean isHub)
            throws CouldNotConnectException, ClientNotSubscribedException, ServiceDownException,
        InvalidSubscriberIdException {
        // Validate that the format of the subscriberId is valid either as a
        // local or hub subscriber.
        if (!isValidSubscriberId(subscriberId, isHub)) {
            throw new InvalidSubscriberIdException("SubscriberId passed is not valid: " + subscriberId.toStringUtf8()
                                                   + ", isHub: " + isHub);
        }
        // Synchronously close the subscription on the client side. Even
        // if the unsubscribe request to the server errors out, we won't be
        // delivering messages for this subscription to the client. The client
        // can later retry the unsubscribe request to the server so they are
        // "fully" unsubscribed from the given topic.
        closeSubscription(topic, subscriberId);
        try {
            subUnsub(topic, subscriberId, OperationType.UNSUBSCRIBE, null);
        } catch (ClientAlreadySubscribedException e) {
            logger.error("Unexpected Exception thrown: " + e.toString());
            // This exception should never be thrown here. But just in case,
            // throw a generic ServiceDownException but wrap the original
            // Exception within it.
            throw new ServiceDownException(e);
        }
    }

    public void asyncUnsubscribe(final ByteString topic, final ByteString subscriberId, final Callback<Void> callback,
                                 final Object context) {
        asyncUnsubscribe(topic, subscriberId, callback, context, false);
    }

    protected void asyncUnsubscribe(final ByteString topic, final ByteString subscriberId,
                                    final Callback<Void> callback, final Object context, boolean isHub) {
        // Validate that the format of the subscriberId is valid either as a
        // local or hub subscriber.
        if (!isValidSubscriberId(subscriberId, isHub)) {
            callback.operationFailed(context, new ServiceDownException(new InvalidSubscriberIdException(
                                         "SubscriberId passed is not valid: " + subscriberId.toStringUtf8() + ", isHub: " + isHub)));
            return;
        }
        // Asynchronously close the subscription. On the callback to that
        // operation once it completes, post the async unsubscribe request.
        asyncCloseSubscription(topic, subscriberId, new Callback<Void>() {
            @Override
            public void operationFinished(Object ctx, Void resultOfOperation) {
                asyncSubUnsub(topic, subscriberId, callback, context, OperationType.UNSUBSCRIBE, null);
            }

            @Override
            public void operationFailed(Object ctx, PubSubException exception) {
                callback.operationFailed(context, exception);
            }
        }, null);
    }

    // This is a helper method to determine if a subscriberId is valid as either
    // a hub or local subscriber
    private boolean isValidSubscriberId(ByteString subscriberId, boolean isHub) {
        if ((isHub && !SubscriptionStateUtils.isHubSubscriber(subscriberId))
                || (!isHub && SubscriptionStateUtils.isHubSubscriber(subscriberId)))
            return false;
        else
            return true;
    }

    public void consume(ByteString topic, ByteString subscriberId, MessageSeqId messageSeqId)
            throws ClientNotSubscribedException {
        if (logger.isDebugEnabled())
            logger.debug("Calling consume for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8() + ", messageSeqId: " + messageSeqId);
        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, subscriberId);
        // Check that this topic subscription on the client side exists.
        if (!topicSubscriber2Channel.containsKey(topicSubscriber)) {
            throw new ClientNotSubscribedException(
                "Cannot send consume message since client is not subscribed to topic: " + topic.toStringUtf8()
                + ", subscriberId: " + subscriberId.toStringUtf8());
        }
        PubSubData pubSubData = new PubSubData(topic, null, subscriberId, OperationType.CONSUME, null, null, null);
        // Send the consume message to the server using the same subscribe
        // channel that the topic subscription uses.
        doConsume(pubSubData, topicSubscriber2Channel.get(topicSubscriber), messageSeqId);
    }

    /**
     * This is a helper method to write the actual subscribe/unsubscribe message
     * once the client is connected to the server and a Channel is available.
     *
     * @param pubSubData
     *            Subscribe/Unsubscribe call's data wrapper object. We assume
     *            that the operationType field is either SUBSCRIBE or
     *            UNSUBSCRIBE.
     * @param channel
     *            Netty I/O channel for communication between the client and
     *            server
     */
    protected void doSubUnsub(PubSubData pubSubData, Channel channel) {
        // Create a PubSubRequest
        PubSubRequest.Builder pubsubRequestBuilder = PubSubRequest.newBuilder();
        pubsubRequestBuilder.setProtocolVersion(ProtocolVersion.VERSION_ONE);
        pubsubRequestBuilder.setType(pubSubData.operationType);
        if (pubSubData.triedServers != null && pubSubData.triedServers.size() > 0) {
            pubsubRequestBuilder.addAllTriedServers(pubSubData.triedServers);
        }
        long txnId = client.globalCounter.incrementAndGet();
        pubsubRequestBuilder.setTxnId(txnId);
        pubsubRequestBuilder.setShouldClaim(pubSubData.shouldClaim);
        pubsubRequestBuilder.setTopic(pubSubData.topic);

        // Create either the Subscribe or Unsubscribe Request
        if (pubSubData.operationType.equals(OperationType.SUBSCRIBE)) {
            // Create the SubscribeRequest
            SubscribeRequest.Builder subscribeRequestBuilder = SubscribeRequest.newBuilder();
            subscribeRequestBuilder.setSubscriberId(pubSubData.subscriberId);
            subscribeRequestBuilder.setCreateOrAttach(pubSubData.createOrAttach);
            // For now, all subscribes should wait for all cross-regional
            // subscriptions to be established before returning.
            subscribeRequestBuilder.setSynchronous(true);

            // Set the SubscribeRequest into the outer PubSubRequest
            pubsubRequestBuilder.setSubscribeRequest(subscribeRequestBuilder);
        } else {
            // Create the UnSubscribeRequest
            UnsubscribeRequest.Builder unsubscribeRequestBuilder = UnsubscribeRequest.newBuilder();
            unsubscribeRequestBuilder.setSubscriberId(pubSubData.subscriberId);

            // Set the UnsubscribeRequest into the outer PubSubRequest
            pubsubRequestBuilder.setUnsubscribeRequest(unsubscribeRequestBuilder);
        }

        // Update the PubSubData with the txnId and the requestWriteTime
        pubSubData.txnId = txnId;
        pubSubData.requestWriteTime = System.currentTimeMillis();

        // Before we do the write, store this information into the
        // ResponseHandler so when the server responds, we know what
        // appropriate Callback Data to invoke for the given txn ID.
        HedwigClientImpl.getResponseHandlerFromChannel(channel).txn2PubSubData.put(txnId, pubSubData);

        // Finally, write the Subscribe request through the Channel.
        if (logger.isDebugEnabled())
            logger.debug("Writing a SubUnsub request to host: " + HedwigClientImpl.getHostFromChannel(channel)
                         + " for pubSubData: " + pubSubData);
        ChannelFuture future = channel.write(pubsubRequestBuilder.build());
        future.addListener(new WriteCallback(pubSubData, client));
    }

    /**
     * This is a helper method to write a consume message to the server after a
     * subscribe Channel connection is made to the server and messages are being
     * consumed by the client.
     *
     * @param pubSubData
     *            Consume call's data wrapper object. We assume that the
     *            operationType field is CONSUME.
     * @param channel
     *            Netty I/O channel for communication between the client and
     *            server
     * @param messageSeqId
     *            Message Seq ID for the latest/last message the client has
     *            consumed.
     */
    public void doConsume(final PubSubData pubSubData, final Channel channel, final MessageSeqId messageSeqId) {
        // Create a PubSubRequest
        PubSubRequest.Builder pubsubRequestBuilder = PubSubRequest.newBuilder();
        pubsubRequestBuilder.setProtocolVersion(ProtocolVersion.VERSION_ONE);
        pubsubRequestBuilder.setType(OperationType.CONSUME);
        long txnId = client.globalCounter.incrementAndGet();
        pubsubRequestBuilder.setTxnId(txnId);
        pubsubRequestBuilder.setTopic(pubSubData.topic);

        // Create the ConsumeRequest
        ConsumeRequest.Builder consumeRequestBuilder = ConsumeRequest.newBuilder();
        consumeRequestBuilder.setSubscriberId(pubSubData.subscriberId);
        consumeRequestBuilder.setMsgId(messageSeqId);

        // Set the ConsumeRequest into the outer PubSubRequest
        pubsubRequestBuilder.setConsumeRequest(consumeRequestBuilder);

        // For Consume requests, we will send them from the client in a fire and
        // forget manner. We are not expecting the server to send back an ack
        // response so no need to register this in the ResponseHandler. There
        // are no callbacks to invoke since this isn't a client initiated
        // action. Instead, just have a future listener that will log an error
        // message if there was a problem writing the consume request.
        if (logger.isDebugEnabled())
            logger.debug("Writing a Consume request to host: " + HedwigClientImpl.getHostFromChannel(channel)
                         + " with messageSeqId: " + messageSeqId + " for pubSubData: " + pubSubData);
        ChannelFuture future = channel.write(pubsubRequestBuilder.build());
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    logger.error("Error writing a Consume request to host: " + HedwigClientImpl.getHostFromChannel(channel)
                                 + " with messageSeqId: " + messageSeqId + " for pubSubData: " + pubSubData);
                }
            }
        });

    }

    public boolean hasSubscription(ByteString topic, ByteString subscriberId) throws CouldNotConnectException,
        ServiceDownException {
        // The subscription type of info should be stored on the server end, not
        // the client side. Eventually, the server will have the Subscription
        // Manager part that ties into Zookeeper to manage this info.
        // Commenting out these type of API's related to that here for now until
        // this data is available on the server. Will figure out what the
        // correct way to contact the server to get this info is then.
        // The client side just has soft memory state for client subscription
        // information.
        return topicSubscriber2Channel.containsKey(new TopicSubscriber(topic, subscriberId));
    }

    public List<ByteString> getSubscriptionList(ByteString subscriberId) throws CouldNotConnectException,
        ServiceDownException {
        // Same as the previous hasSubscription method, this data should reside
        // on the server end, not the client side.
        return null;
    }

    public void startDelivery(final ByteString topic, final ByteString subscriberId, MessageHandler messageHandler)
            throws ClientNotSubscribedException {
        if (logger.isDebugEnabled())
            logger.debug("Starting delivery for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8());
        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, subscriberId);
        // Make sure we know about this topic subscription on the client side
        // exists. The assumption is that the client should have in memory the
        // Channel created for the TopicSubscriber once the server has sent
        // an ack response to the initial subscribe request.
        if (!topicSubscriber2Channel.containsKey(topicSubscriber)) {
            logger.error("Client is not yet subscribed to topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8());
            throw new ClientNotSubscribedException("Client is not yet subscribed to topic: " + topic.toStringUtf8()
                                                   + ", subscriberId: " + subscriberId.toStringUtf8());
        }

        // Register the MessageHandler with the subscribe Channel's
        // Response Handler.
        Channel topicSubscriberChannel = topicSubscriber2Channel.get(topicSubscriber);
        HedwigClientImpl.getResponseHandlerFromChannel(topicSubscriberChannel).getSubscribeResponseHandler()
        .setMessageHandler(messageHandler);
        // Now make the TopicSubscriber Channel readable (it is set to not be
        // readable when the initial subscription is done). Note that this is an
        // asynchronous call. If this fails (not likely), the futureListener
        // will just log an error message for now.
        ChannelFuture future = topicSubscriberChannel.setReadable(true);
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    logger.error("Unable to make subscriber Channel readable in startDelivery call for topic: "
                                 + topic.toStringUtf8() + ", subscriberId: " + subscriberId.toStringUtf8());
                }
            }
        });
    }

    public void stopDelivery(final ByteString topic, final ByteString subscriberId) throws ClientNotSubscribedException {
        if (logger.isDebugEnabled())
            logger.debug("Stopping delivery for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8());
        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, subscriberId);
        // Make sure we know that this topic subscription on the client side
        // exists. The assumption is that the client should have in memory the
        // Channel created for the TopicSubscriber once the server has sent
        // an ack response to the initial subscribe request.
        if (!topicSubscriber2Channel.containsKey(topicSubscriber)) {
            logger.error("Client is not yet subscribed to topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8());
            throw new ClientNotSubscribedException("Client is not yet subscribed to topic: " + topic.toStringUtf8()
                                                   + ", subscriberId: " + subscriberId.toStringUtf8());
        }

        // Unregister the MessageHandler for the subscribe Channel's
        // Response Handler.
        Channel topicSubscriberChannel = topicSubscriber2Channel.get(topicSubscriber);
        HedwigClientImpl.getResponseHandlerFromChannel(topicSubscriberChannel).getSubscribeResponseHandler()
        .setMessageHandler(null);
        // Now make the TopicSubscriber channel not-readable. This will buffer
        // up messages if any are sent from the server. Note that this is an
        // asynchronous call. If this fails (not likely), the futureListener
        // will just log an error message for now.
        ChannelFuture future = topicSubscriberChannel.setReadable(false);
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    logger.error("Unable to make subscriber Channel not readable in stopDelivery call for topic: "
                                 + topic.toStringUtf8() + ", subscriberId: " + subscriberId.toStringUtf8());
                }
            }
        });
    }

    public void closeSubscription(ByteString topic, ByteString subscriberId) throws ServiceDownException {
        PubSubData pubSubData = new PubSubData(topic, null, subscriberId, null, null, null, null);
        synchronized (pubSubData) {
            PubSubCallback pubSubCallback = new PubSubCallback(pubSubData);
            asyncCloseSubscription(topic, subscriberId, pubSubCallback, null);
            try {
                while (!pubSubData.isDone)
                    pubSubData.wait();
            } catch (InterruptedException e) {
                throw new ServiceDownException("Interrupted Exception while waiting for asyncCloseSubscription call");
            }
            // Check from the PubSubCallback if it was successful or not.
            if (!pubSubCallback.getIsCallSuccessful()) {
                throw new ServiceDownException("Exception while trying to close the subscription for topic: "
                                               + topic.toStringUtf8() + ", subscriberId: " + subscriberId.toStringUtf8());
            }
        }
    }

    public void asyncCloseSubscription(final ByteString topic, final ByteString subscriberId,
                                       final Callback<Void> callback, final Object context) {
        if (logger.isDebugEnabled())
            logger.debug("Closing subscription asynchronously for topic: " + topic.toStringUtf8() + ", subscriberId: "
                         + subscriberId.toStringUtf8());
        TopicSubscriber topicSubscriber = new TopicSubscriber(topic, subscriberId);
        if (topicSubscriber2Channel.containsKey(topicSubscriber)) {
            // Remove all cached references for the TopicSubscriber
            Channel channel = topicSubscriber2Channel.get(topicSubscriber);
            topicSubscriber2Channel.remove(topicSubscriber);
            // Close the subscribe channel asynchronously.
            HedwigClientImpl.getResponseHandlerFromChannel(channel).channelClosedExplicitly = true;
            ChannelFuture future = channel.close();
            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (!future.isSuccess()) {
                        logger.error("Failed to close the subscription channel for topic: " + topic.toStringUtf8()
                                     + ", subscriberId: " + subscriberId.toStringUtf8());
                        callback.operationFailed(context, new ServiceDownException(
                                                     "Failed to close the subscription channel for topic: " + topic.toStringUtf8()
                                                     + ", subscriberId: " + subscriberId.toStringUtf8()));
                    } else {
                        callback.operationFinished(context, null);
                    }
                }
            });
        } else {
            logger.warn("Trying to close a subscription when we don't have a subscribe channel cached for topic: "
                        + topic.toStringUtf8() + ", subscriberId: " + subscriberId.toStringUtf8());
            callback.operationFinished(context, null);
        }
    }

    // Public getter and setters for entries in the topic2Host Map.
    // This is used for classes that need this information but are not in the
    // same classpath.
    public Channel getChannelForTopic(TopicSubscriber topic) {
        return topicSubscriber2Channel.get(topic);
    }

    public void setChannelForTopic(TopicSubscriber topic, Channel channel) {
        topicSubscriber2Channel.put(topic, channel);
    }

    public void removeChannelForTopic(TopicSubscriber topic) {
        topicSubscriber2Channel.remove(topic);
    }

}
TOP

Related Classes of org.apache.hedwig.client.netty.HedwigSubscriber

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.