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

cloud.artik.websocket.WebSocketProxy Maven / Gradle / Ivy

package cloud.artik.websocket;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import okio.Buffer;
import okio.BufferedSource;
import cloud.artik.model.AckEnvelope;
import cloud.artik.model.ActionOut;
import cloud.artik.model.ErrorEnvelope;
import cloud.artik.model.MessageOut;
import cloud.artik.model.WebSocketError;

import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.okhttp.ws.WebSocket;
import com.squareup.okhttp.ws.WebSocketCall;
import com.squareup.okhttp.ws.WebSocketListener;

public class WebSocketProxy implements WebSocketListener {
    protected Request request = null;

    protected WebSocket webSocket = null;
    protected ArtikCloudWebSocketCallback callback = null;
    protected OkHttpClient client = null;
    
    protected final JSON json = new JSON();

    protected final ScheduledExecutorService scheduler = Executors
            .newScheduledThreadPool(1);
    protected ConnectionStatus status = ConnectionStatus.CLOSED;

    protected CountDownLatch openSignal = null;
    protected CountDownLatch closeSignal = null;

    protected static String host = "wss://api.artik.cloud/v1.1";

    public WebSocketProxy(String url, OkHttpClient client,
            ArtikCloudWebSocketCallback callback) {
        this.request = new Request.Builder().url(host + url).build();
        this.client = client;
        this.client.setReadTimeout(35, TimeUnit.SECONDS);
        this.callback = callback;
    }

    public static void setHost(String host) {
        WebSocketProxy.host = host;
    }

    public ConnectionStatus getConnectionStatus() {
        return status;
    }

    @Override
    public final void onOpen(WebSocket webSocket, Response response) {
        this.status = ConnectionStatus.CONNECTED;
        this.webSocket = webSocket;

        if (openSignal != null) {
            openSignal.countDown();
        }
        this.callback.onOpen(response.code(), response.message());
    }

    @Override
    public final void onClose(int code, String reason) {
        if (closeSignal != null) {
            closeSignal.countDown();
        }
        this.status = ConnectionStatus.CLOSED;
        this.callback.onClose(code, reason, true);
    }

    @Override
    public final void onFailure(IOException exc, Response response) {
        if (closeSignal != null) {
            closeSignal.countDown();
        }
        this.status = ConnectionStatus.CLOSED;

        WebSocketError error = new WebSocketError();
        if (response != null) {
            error.setCode(response.code());
            error.setMessage(response.message());
        } else {
            error.setCode(-1); // Read Timeout
            if (exc.getCause() != null) {
                error.setMessage(exc.getCause().getMessage());
            } else {
                error.setMessage(exc.getMessage());    
            }
        }
        
        this.callback.onError(error);
    }

    @SuppressWarnings("unchecked")
    @Override
    public final void onMessage(ResponseBody response)
            throws IOException {

        BufferedSource source = response.source();
        try {
            MediaType contentType = response.contentType();
            //System.out.println(contentType);
            String message = source.readString(contentType.charset());
            //String message = source.readString(Charset.defaultCharset());

            Map jsonMap = (Map) json.getGson()
                    .fromJson(message, Map.class);
            if (jsonMap.containsKey("error")) {
                // Check if it is a rate limit
                // Have to treat it differently.
                ErrorEnvelope artikError = json.getGson().fromJson(message,
                        ErrorEnvelope.class);
                this.callback.onError(artikError.getError());
            } else if (jsonMap.containsKey("type")) {
                String type = (String) jsonMap.get("type");

                if ("ping".equalsIgnoreCase(type)) {
                    // Ping
                    long ts = ((Double) jsonMap.get("ts")).longValue();
                    this.callback.onPing(ts);
                } else if ("message".equalsIgnoreCase(type)) {
                    // Message
                    MessageOut artikMessage = json.getGson().fromJson(message,
                            MessageOut.class);
                    this.callback.onMessage(artikMessage);
                } else if ("action".equalsIgnoreCase(type)) {
                    // Action
                    ActionOut artikAction = json.getGson().fromJson(message,
                            ActionOut.class);
                    this.callback.onAction(artikAction);
                }
            } else if (jsonMap.containsKey("data")
                    && jsonMap.containsKey("mid")) {
                // Message, in this case we don't have a way to check the type,
                // although we could assume it's a message since it's the
                // default
                MessageOut artikMessage = json.getGson().fromJson(message,
                        MessageOut.class);
                this.callback.onMessage(artikMessage);
            } else if (jsonMap.containsKey("data")
                    && ((Map) jsonMap.get("data"))
                            .containsKey("mid")
                    || ((Map) jsonMap.get("data"))
                            .containsKey("code")) {
                // Register/Action Ack
                AckEnvelope ackEnv = json.getGson().fromJson(message,
                        AckEnvelope.class);
                this.callback.onAck(ackEnv.getData());
            } else {
                // Message Ack?
                System.err.println("Un handled message: " + json);
            }
        } catch (Exception exc) {
            // Couldn't parse JSON. Shouldnt happen!
            System.err.println(exc.getMessage());
        } finally {
            source.close();
        }
    }

    @Override
    public final void onPong(Buffer arg0) {
        // TODO Pong

    }

    public final void connect() throws IOException {
        if (this.status != ConnectionStatus.CONNECTING) {
            this.status = ConnectionStatus.CONNECTING;
            WebSocketCall ws = WebSocketCall.create(this.client, this.request);
            ws.enqueue(this);
        }
    }

    public final void connectBlocking() throws IOException,
            InterruptedException {
        if (openSignal == null) {
            openSignal = new CountDownLatch(1);
        }
        this.connect();

        openSignal.await();
        openSignal = null;
    }

    public final void close() throws IOException {
        if (this.status != ConnectionStatus.CLOSING) {
            this.status = ConnectionStatus.CLOSING;

            this.webSocket.close(2000, "OK");
        }
    }

    public final void closeBlocking() throws IOException, InterruptedException {
        if (closeSignal == null) {
            closeSignal = new CountDownLatch(1);
        }
        this.close();
        closeSignal.await();

        this.client.getDispatcher().getExecutorService().shutdownNow();
        this.client.getDispatcher().getExecutorService()
                .awaitTermination(100, TimeUnit.MILLISECONDS);

        closeSignal = null;
    }

    protected void sendObject(Object object) throws IOException {
        if (!client.getDispatcher().getExecutorService().isShutdown()) {
            RequestBody request = RequestBody.create(WebSocket.TEXT, this.json.getGson().toJson(object));

            webSocket.sendMessage(request);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy