io.s4.client.Driver.java Source code

Java tutorial

Introduction

Here is the source code for io.s4.client.Driver.java

Source

/*
 * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
 * 
 * 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. See accompanying LICENSE file. 
 */
package io.s4.client;

import io.s4.client.util.ByteArrayIOChannel;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * S4 Client Driver.
 * 
 * Allows S4 Client code to send and receive events from an S4 cluster.
 */
public class Driver {
    private static final String protocolName = "generic-json";
    private static final int versionMajor = 1;
    private static final int versionMinor = 0;

    protected String uuid = null;
    protected State state = State.Null;

    protected final String hostname;
    protected final int port;

    protected Socket sock = null;
    protected ByteArrayIOChannel io = null;

    protected ReadMode readMode = ReadMode.Private;
    protected List<String> readInclude = new ArrayList<String>();
    protected List<String> readExclude = new ArrayList<String>();
    protected WriteMode writeMode = WriteMode.Enabled;

    protected boolean debug = false;

    protected int recvTimeoutMs = 0;

    /**
     * Configure driver with Adapter location.
     * 
     * Note: this does not create a connection to the adapter.
     * 
     * @see #init()
     * @see #connect(ReadMode, WriteMode)
     * 
     * @param hostname
     *            Name of S4 client adapter host.
     * @param port
     *            Port on which adapter listens for client connections.
     */
    public Driver(String hostname, int port) {
        this.hostname = hostname;
        this.port = port;
    }

    /**
     * Set the read mode, if not already connected.
     * 
     * @param m
     *            read mode
     * @return Driver with read mode set to {@code m}
     */
    public Driver setReadMode(ReadMode m) {
        if (state != State.Connected)
            this.readMode = m;
        return this;
    }

    /**
     * Add to set of stream names included for reading, if not already
     * connected.
     * 
     * @param s
     *            list of stream names
     * @return Updated driver
     */
    public Driver readInclude(List<String> s) {
        if (state != State.Connected)
            this.readInclude.addAll(s);
        return this;
    }

    /**
     * Add to set of stream names included for reading, if not already
     * connected.
     * 
     * @param s
     *            list of stream names
     * @return Updated driver
     */
    public Driver readInclude(String[] s) {
        return readInclude(Arrays.asList(s));
    }

    /**
     * Add to set of stream names included for reading, if not already
     * connected.
     * 
     * @param s
     *            stream name
     * @return Updated driver
     */
    public Driver readInclude(String s) {
        if (state != State.Connected)
            this.readInclude.add(s);
        return this;
    }

    /**
     * Add to set of stream names excluded from reading, if not already
     * connected.
     * 
     * @param s
     *            list of stream names
     * @return Updated driver
     */
    public Driver readExclude(List<String> s) {
        if (state != State.Connected)
            this.readExclude.addAll(s);
        return this;
    }

    /**
     * Add to set of stream names excluded from reading, if not already
     * connected.
     * 
     * @param s
     *            list of stream names
     * @return Updated driver
     */
    public Driver readExclude(String[] s) {
        return readExclude(Arrays.asList(s));
    }

    /**
     * Add to set of stream names excluded from reading, if not already
     * connected.
     * 
     * @param s
     *            stream name
     * @return Updated driver
     */
    public Driver readExclude(String s) {
        if (state != State.Connected)
            this.readExclude.add(s);
        return this;
    }

    /**
     * Set the write mode, if not already connected.
     * 
     * @param m
     *            write mode
     * @return Driver with write mode set to {@code m}
     */
    public Driver setWriteMode(WriteMode m) {
        if (state != State.Connected)
            this.writeMode = m;
        return this;
    }

    /**
     * 
     * @param debug
     *            debug flag
     * @return Driver with debug flag set to {@code debug}
     */
    public Driver setDebug(boolean debug) {
        this.debug = debug;
        return this;
    }

    /**
     * Set the timeout for receiving data.
     * 
     * @param ms
     *            timeout in milliseconds
     * @return updated driver
     */
    public Driver setRecvTimeout(int ms) {
        this.recvTimeoutMs = ms;
        return this;
    }

    /**
     * Get the state of the driver.
     * 
     * @return state
     */
    public State getState() {
        return state;
    }

    /**
     * Initialize the driver.
     * 
     * Handshake with adapter to receive a unique id, and verify that the driver
     * is compatible with the protocol used by the adapter. This does not
     * actually establish a connection for sending and receiving events.
     * 
     * @see #connect(ReadMode, WriteMode)
     * 
     * @return true if and only if the adapter issued a valid ID to this client,
     *         and the protocol is found to be compatible.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */
    public boolean init() throws IOException {
        if (state.isInitialized())
            return true;

        try {
            sock = new Socket(hostname, port);

            ByteArrayIOChannel io = new ByteArrayIOChannel(sock);

            io.send(emptyBytes);

            byte[] b = io.recv();

            if (b == null || b.length == 0) {
                if (debug) {
                    System.err.println("Empty response during initialization.");
                }
                return false;
            }

            JSONObject json = new JSONObject(new String(b));

            this.uuid = json.getString("uuid");

            JSONObject proto = json.getJSONObject("protocol");

            if (!isCompatible(proto)) {
                if (debug) {
                    System.err.println("Driver not compatible with adapter protocol: " + proto);
                }
                return false;
            }

            state = State.Initialized;

            return true;

        } catch (JSONException e) {
            if (debug) {
                System.err.println("malformed JSON in initialization response. " + e);
            }
            e.printStackTrace();
            return false;

        } finally {
            sock.close();
        }
    }

    /**
     * Test if the adapter protocol is compatible with this driver. More
     * specifically, the following must be true:
     * {@code
     *     p.name == this.protocolName()
     * AND p.versionMajor == this.versionMajor()
     * AND p.versionMinor >= this.versionMinor()
     * }
     * 
     * @param p
     *            protocol specifier.
     * @return true if and only if the protocol is compatible.
     * @throws JSONException
     *             if some required field was not found in the protocol
     *             specifier.
     */
    private boolean isCompatible(JSONObject p) throws JSONException {
        return p.getString("name").equals(protocolName) && (p.getInt("versionMajor") == versionMajor)
                && (p.getInt("versionMinor") >= versionMinor);
    }

    /**
     * Establish a connection to the adapter. Upon success, this enables the
     * client to send and receive events. The client must first be initialized.
     * Otherwise, this operation will fail.
     * 
     * @see #init()
     * 
     * @return true if and only if a connection was successfully established.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */
    public boolean connect() throws IOException {
        if (!state.isInitialized()) {
            // must first be initialized
            if (debug) {
                System.err.println("Not initialized.");
            }
            return false;
        } else if (state.isConnected()) {
            // nothing to do if already connected.
            return true;
        }

        String message = null;

        try {
            // constructing connect message
            JSONObject json = new JSONObject();

            json.put("uuid", uuid);
            json.put("readMode", readMode.toString());
            json.put("writeMode", writeMode.toString());

            if (readInclude != null) {
                // stream inclusion
                json.put("readInclude", new JSONArray(readInclude));
            }

            if (readExclude != null) {
                // stream exclusion
                json.put("readExclude", new JSONArray(readExclude));
            }

            message = json.toString();

        } catch (JSONException e) {
            if (debug) {
                System.err.println("error constructing connect message: " + e);
            }
            return false;
        }

        try {
            // send the message
            this.sock = new Socket(hostname, port);
            this.io = new ByteArrayIOChannel(sock);

            io.send(message.getBytes());

            // get a response
            byte[] b = io.recv();

            if (b == null || b.length == 0) {
                if (debug) {
                    System.err.println("empty response from adapter during connect.");
                }
                return false;
            }

            String response = new String(b);

            JSONObject json = new JSONObject(response);
            String s = json.optString("status", "unknown");

            // does it look OK?
            if (s.equalsIgnoreCase("ok")) {
                // done connecting
                state = State.Connected;
                return true;
            } else if (s.equalsIgnoreCase("failed")) {
                // server has failed the connect attempt
                if (debug) {
                    System.err.println("connect failed by adapter. reason: " + json.optString("reason", "unknown"));
                }
                return false;
            } else {
                // unknown response.
                if (debug) {
                    System.err.println("connect failed by adapter. unrecongnized response: " + response);
                }
                return false;
            }

        } catch (Exception e) {
            // clean up after error...
            if (debug) {
                System.err.println("error during connect: " + e);
                e.printStackTrace();
            }

            if (this.sock.isConnected()) {
                this.sock.close();

            }

            return false;
        }
    }

    /**
     * Close the connection to the adapter. Events can no longer be sent or
     * received by the client.
     * 
     * @return true upon success. False if the connection is already closed.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */
    public boolean disconnect() throws IOException {
        if (state.isConnected()) {
            io.send(emptyBytes);
            state = State.Null;
            return true;
        }

        return false;
    }

    /**
     * Send a message to the adapter.
     * 
     * @param m
     *            message to be sent
     * @return true if and only if the message was successfully sent.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */

    public boolean send(Message m) throws IOException {

        if (!state.isConnected()) {
            if (debug) {
                System.err.println("send failed. not connected.");
            }
            return false;
        }

        String s = null;

        try {
            JSONObject json = new JSONObject();

            m.toJson(json);

            s = json.toString();

            io.send(s.getBytes());

            return true;

        } catch (JSONException e) {
            if (debug) {
                System.err.println("exception while constructing message to send: " + e);
            }
            return false;
        }
    }

    /**
     * Receive a message from the adapter. This function blocks till a message
     * becomes available to read. The set of messages sent to this client from
     * S4 depends on the read mode of the client.
     * 
     * @see ReadMode
     * 
     * @return message received from S4 cluster.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */
    public Message recv() throws IOException {
        return recv(this.recvTimeoutMs);
    }

    /**
     * Receive a message from the adapter. This function blocks till a message
     * becomes available to read, or till a specified number of milliseconds
     * have elapsed. The set of messages sent to this client from S4 depends on
     * the read mode of the client.
     * 
     * @see ReadMode
     * 
     * @param timeout
     *            timeout in milliseconds
     * @return message received from S4 cluster.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     * @throws SocketTimeoutException
     *             if the read timed out.
     */
    public Message recv(int timeout) throws IOException {
        if (!state.isConnected()) {
            if (debug) {
                System.err.println("recv failed. not connected.");
            }
            return null;
        }

        try {
            byte[] b = io.recv(timeout);

            if (b == null || b.length == 0) {
                if (debug) {
                    System.err.println("empty message from adapter. disconnecting");
                }
                this.disconnect();
                return null;
            }

            String s = new String(b);

            JSONObject json = new JSONObject(s);

            return Message.fromJson(json);

        } catch (SocketTimeoutException e) {
            if (debug) {
                System.err.println("recv timed out");
            }
            return null;

        } catch (JSONException e) {
            if (debug) {
                System.err.println("exception while parsing received JSON: " + e);
            }
            return null;
        }
    }

    /**
     * Receive the set of message that from the adapter within a specified time
     * interval.
     * 
     * @param t
     *            interval in milliseconds
     * @return messages received from S4 cluster.
     * @throws IOException
     *             if the underlying TCP/IP socket throws an exception.
     */
    public List<Message> recvAll(int t) throws IOException {
        if (!state.isConnected()) {
            if (debug) {
                System.err.println("recv failed. not connected.");
            }
            return Collections.<Message>emptyList();
        }

        List<Message> messages = new ArrayList<Message>();

        long tStart = System.currentTimeMillis();
        long tEnd = tStart + t;

        long tNow = tStart;

        while (tNow < tEnd) {
            try {
                byte[] b = io.recv((int) (tEnd - tNow));

                if (b == null || b.length == 0) {
                    if (debug) {
                        System.err.println("empty message from adapter. disconnecting");
                    }
                    this.disconnect();
                    break;
                }

                String s = new String(b);

                JSONObject json = new JSONObject(s);

                messages.add(Message.fromJson(json));

            } catch (SocketTimeoutException e) {
                break;

            } catch (JSONException e) {
                if (debug) {
                    System.err.println("exception while parsing received JSON: " + e);
                }
            }

            tNow = System.currentTimeMillis();
        }

        return messages;
    }

    /**
     * State of the client.
     */
    public enum State {
        /**
         * Uninitialized.
         */
        Null(false, false),

        /**
         * Initialized, but not connected to S4 adapter.
         */
        Initialized(true, false),

        /**
         * Connected to S4 adapter (implies initialized).
         */
        Connected(true, true);

        State(boolean initialized, boolean connected) {
            this.initialized = initialized;
            this.connected = connected;
        }

        private final boolean initialized;
        private final boolean connected;

        /**
         * Is initialization completed?
         * 
         * @return true if and only if this state implies initialization has
         *         been completed.
         */
        public boolean isInitialized() {
            return initialized;
        }

        /**
         * Is client connected to S4 adapter?
         * 
         * @return true if and only if the client is connected to S4 adapter.
         */
        public boolean isConnected() {
            return connected;
        }
    };

    private static final byte[] emptyBytes = {};

    /**
     * Reads and prints all events over an interval of 5 seconds from a set of
     * streams specified on the command line.
     * 
     * @param argv
     *            list of streams
     * @throws IOException
     *             if an error occurs while reading from adapter.
     */
    public static void main(String[] argv) throws IOException {
        Driver d = new Driver("localhost", 2334);

        System.out.println("State: " + d.getState());

        d.init();

        System.out.println("State: " + d.getState());

        d.setReadMode(ReadMode.Select).readInclude(argv).setWriteMode(WriteMode.Enabled);

        d.connect();

        System.out.println("State: " + d.getState());

        List<Message> mm = d.recvAll(5001);
        System.out.println("got messages (" + mm.size() + "): " + mm);

        System.out.println("Disconnecting...");
        d.disconnect();

        System.out.println("State: " + d.getState());
    }
}