All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pubnub.api.managers.SubscriptionManager Maven / Gradle / Ivy

Go to download

PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter second!

There is a newer version: 4.6.5
Show newest version
package com.pubnub.api.managers;

import com.pubnub.api.PubNub;
import com.pubnub.api.builder.dto.StateOperation;
import com.pubnub.api.builder.dto.SubscribeOperation;
import com.pubnub.api.builder.dto.UnsubscribeOperation;
import com.pubnub.api.callbacks.PNCallback;
import com.pubnub.api.callbacks.ReconnectionCallback;
import com.pubnub.api.callbacks.SubscribeCallback;
import com.pubnub.api.endpoints.presence.Heartbeat;
import com.pubnub.api.endpoints.presence.Leave;
import com.pubnub.api.endpoints.pubsub.Subscribe;
import com.pubnub.api.enums.PNHeartbeatNotificationOptions;
import com.pubnub.api.enums.PNStatusCategory;
import com.pubnub.api.models.consumer.PNStatus;
import com.pubnub.api.models.server.Envelope;
import com.pubnub.api.models.server.SubscribeEnvelope;
import com.pubnub.api.models.server.SubscribeMessage;
import com.pubnub.api.workers.SubscribeMessageWorker;
import lombok.extern.slf4j.Slf4j;
import retrofit2.Call;

import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class SubscriptionManager {

    private static final int HEARTBEAT_INTERVAL_MULTIPLIER = 1000;

    private PubNub pubnub;
    private Call subscribeCall;
    private Call heartbeatCall;

    private LinkedBlockingQueue messageQueue;

    /**
     * Store the latest timetoken to subscribe with, null by default to get the latest timetoken.
     */
    private Long timetoken;

    /**
     * Keep track of Region to support PSV2 specification.
     */
    private String region;

    /**
     * Timer for heartbeat operations.
     */
    private Timer timer;

    private StateManager subscriptionState;
    private ListenerManager listenerManager;
    private ReconnectionManager reconnectionManager;

    private Thread consumerThread;

    /**
     * lever to indicate if an announcement to the user about the subscription should be made.
     * the announcement happens only after the channel mix has been changed.
     */
    private boolean subscriptionStatusAnnounced;

    public SubscriptionManager(PubNub pubnubInstance) {
        this.pubnub = pubnubInstance;

        this.subscriptionStatusAnnounced = false;
        this.messageQueue = new LinkedBlockingQueue<>();
        this.subscriptionState = new StateManager();
        this.listenerManager = new ListenerManager(this.pubnub);
        this.reconnectionManager = new ReconnectionManager(this.pubnub);

        this.timetoken = 0L;

        this.reconnectionManager.setReconnectionListener(new ReconnectionCallback() {
            @Override
            public void onReconnection() {
                reconnect();
                PNStatus pnStatus = PNStatus.builder()
                        .error(false)
                        .category(PNStatusCategory.PNReconnectedCategory)
                        .build();

                subscriptionStatusAnnounced = true;
                listenerManager.announce(pnStatus);
            }
        });

        consumerThread = new Thread(new SubscribeMessageWorker(this.pubnub, listenerManager, messageQueue));
        consumerThread.start();
    }

    public final void addListener(SubscribeCallback listener) {
        listenerManager.addListener(listener);
    }

    public final void removeListener(SubscribeCallback listener) {
        listenerManager.removeListener(listener);
    }


    public final synchronized void reconnect() {
        this.startSubscribeLoop();
        this.registerHeartbeatTimer();
    }

    public final synchronized void disconnect() {
        stopHeartbeatTimer();
        stopSubscribeLoop();
    }


    public synchronized void stop() {
        disconnect();
        consumerThread.interrupt();
    }

    public final synchronized void adaptStateBuilder(final StateOperation stateOperation) {
        this.subscriptionState.adaptStateBuilder(stateOperation);
        reconnect();
    }

    public final synchronized void adaptSubscribeBuilder(final SubscribeOperation subscribeOperation) {
        this.subscriptionState.adaptSubscribeBuilder(subscribeOperation);
        // the channel mix changed, on the successful subscribe, there is going to be announcement.
        this.subscriptionStatusAnnounced = false;

        if (subscribeOperation.getTimetoken() != null) {
            this.timetoken = subscribeOperation.getTimetoken();
        }

        reconnect();
    }

    public final synchronized void adaptUnsubscribeBuilder(final UnsubscribeOperation unsubscribeOperation) {
        this.subscriptionState.adaptUnsubscribeBuilder(unsubscribeOperation);

        new Leave(pubnub)
            .channels(unsubscribeOperation.getChannels()).channelGroups(unsubscribeOperation.getChannelGroups())
            .async(new PNCallback() {
                @Override
                public void onResponse(final Boolean result, final PNStatus status) {
                    listenerManager.announce(status);
                }
        });

        reconnect();
    }

    private void registerHeartbeatTimer() {
        // make sure only one timer is running at a time.
        stopHeartbeatTimer();

        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                performHeartbeatLoop();
            }
        }, 0, pubnub.getConfiguration().getHeartbeatInterval() * HEARTBEAT_INTERVAL_MULTIPLIER);

    }

    private void stopHeartbeatTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    private void startSubscribeLoop() {
        // this function can be called from different points, make sure any old loop is closed
        stopSubscribeLoop();

        List combinedChannels = this.subscriptionState.prepareChannelList(true);
        List combinedChannelGroups = this.subscriptionState.prepareChannelGroupList(true);

        // do not start the subscribe loop if we have no channels to subscribe to.
        if (combinedChannels.isEmpty() && combinedChannelGroups.isEmpty()) {
            return;
        }

        subscribeCall = new Subscribe(pubnub)
                .channels(combinedChannels).channelGroups(combinedChannelGroups)
                .timetoken(timetoken).region(region)
                .filterExpression(pubnub.getConfiguration().getFilterExpression())
                .async(new PNCallback() {
                    @Override
                    public void onResponse(final SubscribeEnvelope result, final PNStatus status) {
                        if (status.isError()) {

                            if (status.getCategory() == PNStatusCategory.PNTimeoutCategory) {
                                startSubscribeLoop();
                            } else {
                                disconnect();
                                listenerManager.announce(status);

                                // stop all announcements and ask the reconnection manager to start polling for connection restoration..
                                reconnectionManager.startPolling();
                            }

                            return;
                        }

                        if (!subscriptionStatusAnnounced) {
                            PNStatus pnStatus = PNStatus.builder()
                                    .error(false)
                                    .category(PNStatusCategory.PNConnectedCategory)
                                    .statusCode(status.getStatusCode())
                                    .authKey(status.getAuthKey())
                                    .operation(status.getOperation())
                                    .clientRequest(status.getClientRequest())
                                    .origin(status.getOrigin())
                                    .tlsEnabled(status.isTlsEnabled())
                                    .build();

                            subscriptionStatusAnnounced = true;
                            listenerManager.announce(pnStatus);
                        }

                        if (result.getMessages().size() != 0) {
                            messageQueue.addAll(result.getMessages());
                        }

                        timetoken = result.getMetadata().getTimetoken();
                        region = result.getMetadata().getRegion();
                        startSubscribeLoop();
                    }
                });
    }

    private void stopSubscribeLoop() {
        if (subscribeCall != null && !subscribeCall.isExecuted() && !subscribeCall.isCanceled()) {
            subscribeCall.cancel();
        }
    }

    private void performHeartbeatLoop() {
        if (heartbeatCall != null && !heartbeatCall.isCanceled() && !heartbeatCall.isExecuted()) {
            heartbeatCall.cancel();
        }

        List presenceChannels = this.subscriptionState.prepareChannelList(false);
        List presenceChannelGroups = this.subscriptionState.prepareChannelGroupList(false);
        Map stateStorage = this.subscriptionState.createStatePayload();

        // do not start the loop if we do not have any presence channels or channel groups enabled.
        if (presenceChannels.isEmpty() && presenceChannelGroups.isEmpty()) {
            return;
        }

        heartbeatCall = new Heartbeat(pubnub)
                .channels(presenceChannels).channelGroups(presenceChannelGroups).state(stateStorage)
                .async(new PNCallback() {
                    @Override
                    public void onResponse(Boolean result, PNStatus status) {
                        PNHeartbeatNotificationOptions heartbeatVerbosity = pubnub
                                .getConfiguration().getHeartbeatNotificationOptions();

                        if (status.isError()) {
                            if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL
                                    || heartbeatVerbosity == PNHeartbeatNotificationOptions.FAILURES) {
                                listenerManager.announce(status);
                            }

                        } else {
                            if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL) {
                                listenerManager.announce(status);
                            }
                        }
                    }
                });
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy