com.podio.sdk.push.FayePushClient.java Source code

Java tutorial

Introduction

Here is the source code for com.podio.sdk.push.FayePushClient.java

Source

/*
 *  Copyright (C) 2014 Copyright Citrix Systems, Inc.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
 *  this software and associated documentation files (the "Software"), to deal in
 *  the Software without restriction, including without limitation the rights to
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 *  of the Software, and to permit persons to whom the Software is furnished to
 *  do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */
package com.podio.sdk.push;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.podio.sdk.QueueClient;
import com.podio.sdk.Request.ErrorListener;
import com.podio.sdk.Request.ResultListener;
import com.podio.sdk.domain.push.Event;
import com.podio.sdk.internal.CallbackManager;
import com.podio.sdk.internal.Utils;

/**
 * @author Lszl Urszuly
 */
public class FayePushClient extends QueueClient implements PushClient {

    /**
     * The list of active subscriptions, grouped by channel.
     */
    private final HashMap<String, ArrayList<EventListener>> subscriptions;

    /**
     * Manages the external error listeners.
     */
    private final CallbackManager<Void> callbackManager;

    /**
     * The transport layer to be used by this push implementation.
     */
    private final Transport transport;

    /**
     * Initializes the push client to its default state.
     *
     * @param transport
     *        The transport implementation over which the push events and
     *        configurations will be sent and received.
     */
    public FayePushClient(Transport transport) {
        super(1, 1, 0L);

        this.callbackManager = new CallbackManager<Void>();
        this.subscriptions = new HashMap<String, ArrayList<EventListener>>();
        this.transport = transport;

        // The internal error listener channel between the push client and the
        // transport layer. It's up to this implementation to decide what is
        // facing the caller and what is not.
        this.transport.setErrorListener(new ErrorListener() {

            @Override
            public boolean onErrorOccured(Throwable cause) {
                // Shut down everything and clear any subscriptions.
                if (PushRequest.getState() != PushRequest.State.closed) {
                    Transport transport = FayePushClient.this.transport;
                    execute(new DisconnectRequest(transport));
                    subscriptions.clear();
                }

                // Tell the world.
                callbackManager.deliverErrorOnMainThread(cause);
                return true;
            }

        });

        // The internal event listener channel between the push client and the
        // transport layer. This implementation is responsible for parsing the
        // provided json and call appropriate push event listeners.
        this.transport.setEventListener(new ResultListener<String>() {

            @Override
            public boolean onRequestPerformed(String json) {
                // This one should be running on the main thread.

                // Reconnect if needed.
                if (PushRequest.getState() != PushRequest.State.closed) {
                    Transport transport = FayePushClient.this.transport;
                    ConnectRequest reconnectRequest = new ConnectRequest(transport);
                    execute(reconnectRequest);
                }

                // Parse the delivered json events.
                HashMap<String, ArrayList<Event>> events = parseEventData(json);
                deliverEvents(events);

                return true;
            }

        });
    }

    @Override
    public void publish(String channel, String signature, String timestamp, Object data) {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * Subscribes to the given push channel in the Podio infrastructure. If
     * there already is an existing subscription, the given listener will simply
     * be added to the list of event listeners and the original request will be
     * returned. Note that this request may be null by now as this
     * implementation only keeps a weak reference to it. The caller must
     * therefore check for null pointers.
     */
    @Override
    public void subscribe(String channel, String signature, String timestamp, EventListener listener) {
        if (subscriptions.containsKey(channel)) {
            ArrayList<EventListener> listeners = subscriptions.get(channel);
            listeners.add(listener);
        } else {
            ArrayList<EventListener> listeners = new ArrayList<EventListener>();
            listeners.add(listener);
            subscriptions.put(channel, listeners);
            execute(new SubscribeRequest(channel, signature, timestamp, transport));
        }
    }

    /**
     * Removes the given listener from the stated push channel. If {@code null}
     * is provided as listener, then all listeners will be removed. If there are
     * no more listeners listening for events at the stated channel, a
     * termination request will be sent to the Podio API.
     */
    @Override
    public void unsubscribe(String channel, EventListener listener) {
        if (listener == null) {
            // Remove all subscriptions for the given channel.
            if (subscriptions.containsKey(channel)) {
                subscriptions.remove(channel);
                execute(new UnsubscribeRequest(channel, transport));
            }
        } else {
            // Remove the given listener for the given channel.
            if (subscriptions.containsKey(channel)) {
                ArrayList<EventListener> listeners = subscriptions.get(channel);

                if (listeners != null) {
                    listeners.remove(listener);

                    // If no more listener, then also unsubscribe at API level.
                    if (listeners.size() == 0) {
                        subscriptions.remove(channel);
                        execute(new UnsubscribeRequest(channel, transport));
                    }
                }
            }
        }

        if (subscriptions.isEmpty()) {
            execute(new DisconnectRequest(transport));
        }
    }

    @Override
    public PushClient addErrorListener(ErrorListener errorListener) {
        callbackManager.addErrorListener(errorListener, false, null);
        return this;
    }

    @Override
    public PushClient removeErrorListener(ErrorListener errorListener) {
        callbackManager.removeErrorListener(errorListener);
        return this;
    }

    private HashMap<String, ArrayList<Event>> parseEventData(String json) {
        HashMap<String, ArrayList<Event>> result = new HashMap<String, ArrayList<Event>>();

        if (Utils.isEmpty(json)) {
            return result;
        }

        // Parse the root json element.
        JsonParser jsonParser = new JsonParser();
        JsonElement root = jsonParser.parse(json);
        JsonArray jsonElements;

        // Make sure we're always operating on an array of objects.
        if (root.isJsonArray()) {
            jsonElements = root.getAsJsonArray();
        } else if (root.isJsonObject()) {
            jsonElements = new JsonArray();
            jsonElements.add(root.getAsJsonObject());
        } else {
            return result;
        }

        JsonObject jsonObject;
        Gson gson = new Gson();

        // Search for all data bearing event objects.
        for (JsonElement jsonElement : jsonElements) {
            if (jsonElement.isJsonObject()) {
                jsonObject = jsonElement.getAsJsonObject();

                if (jsonObject.has("channel") && jsonObject.has("data")) {
                    JsonObject fayeData = getJsonObject(jsonObject, "data");
                    JsonObject podioData = getJsonObject(fayeData, "data");

                    String key = getString(jsonObject, "channel");
                    String type = getString(podioData, "event");

                    if (Utils.notAnyEmpty(key, type)) {
                        try {
                            Event.Type eventType = Event.Type.valueOf(type);
                            Event event = gson.fromJson(podioData, eventType.getClassObject());

                            if (result.containsKey(key)) {
                                result.get(key).add(event);
                            } else {
                                ArrayList<Event> events = new ArrayList<Event>();
                                events.add(event);
                                result.put(key, events);
                            }
                        } catch (NullPointerException e) {
                            callbackManager.deliverError(e);
                        } catch (IllegalArgumentException e) {
                            callbackManager.deliverError(e);
                        } catch (JsonSyntaxException e) {
                            callbackManager.deliverError(e);
                        }
                    }
                }
            }
        }

        return result;
    }

    private JsonObject getJsonObject(JsonObject parent, String member) {
        return parent != null && parent.has(member) ? parent.getAsJsonObject(member) : new JsonObject();
    }

    private String getString(JsonObject parent, String member) {
        return parent != null && parent.has(member) ? parent.get(member).getAsString() : "";
    }

    private void deliverEvents(HashMap<String, ArrayList<Event>> events) {
        if (events == null) {
            return;
        }

        // Deliver to their corresponding listeners.
        Set<String> keys = events.keySet();
        for (String key : keys) {
            ArrayList<Event> eventsList = events.get(key);
            Event[] eventsArray = new Event[eventsList.size()];
            eventsList.toArray(eventsArray);

            ArrayList<EventListener> listeners = subscriptions.get(key);

            if (listeners != null) {
                for (EventListener listener : listeners) {
                    if (listener != null) {
                        listener.onEventReceived(eventsArray);
                    }
                }
            }
        }
    }
}