com.comcast.viper.flume2storm.connection.receptor.KryoNetEventReceptor.java Source code

Java tutorial

Introduction

Here is the source code for com.comcast.viper.flume2storm.connection.receptor.KryoNetEventReceptor.java

Source

/**
 * Copyright 2014 Comcast Cable Communications Management, LLC
 *
 * 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.
 */
package com.comcast.viper.flume2storm.connection.receptor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.joda.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.comcast.viper.flume2storm.KryoUtil;
import com.comcast.viper.flume2storm.connection.KryoNetParameters;
import com.comcast.viper.flume2storm.connection.MyKryoSerialization;
import com.comcast.viper.flume2storm.connection.parameters.KryoNetConnectionParameters;
import com.comcast.viper.flume2storm.connection.sender.KryoNetEventSender;
import com.comcast.viper.flume2storm.event.F2SEvent;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.FrameworkMessage.KeepAlive;
import com.esotericsoftware.kryonet.Listener;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

/**
 * A Flume2Storm event receptor implementation using KryoNet framework. Note
 * that the receptor, when started, will always try to reconnect to the KryoNet
 * server. On disconnection, it will retry immediately, and then wait for a
 * specified retry delay before retrying again.
 */
public class KryoNetEventReceptor implements EventReceptor<KryoNetConnectionParameters> {
    protected static final Logger LOG = LoggerFactory.getLogger(KryoNetEventReceptor.class);
    protected final KryoNetParameters kryoNetParameters;
    protected final KryoNetConnectionParameters connectionParams;
    protected final EventReceptorStats stats;
    protected final AtomicReference<ImmutableList<EventReceptorListener>> listeners;
    protected final Queue<F2SEvent> events;
    protected final AtomicBoolean keepRunning;
    protected final AtomicReference<Instant> nextConnectionTime;
    protected MaintainConnection maintainConnectionThread;
    protected Client client;

    // TODO fix sleep time before reconnection attempt

    /**
     * Thread to maintain the connection to the KryoNet server open
     */
    protected class MaintainConnection extends Thread {
        /**
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            LOG.debug("Maintain connection thread started");
            try {
                while (keepRunning.get()) {
                    if (!isConnected() && Instant.now().isAfter(nextConnectionTime.get())) {
                        if (!connect()) {
                            setNextReconnectionTime();
                            LOG.trace("Connection failed. Set next connection attempt to {}",
                                    nextConnectionTime.get());
                        }
                    } else {
                        try {
                            Thread.sleep(100);
                        } catch (final InterruptedException e) {
                            // Nothing to do
                        }
                    }
                }
            } catch (Exception e) {
                LOG.error("Exception in maintain thread: " + e.getMessage(), e);
            }
            cleanup();
            LOG.debug("Maintain connection thread terminated");
        }
    }

    /**
     * @param connectionParams
     *          Connection parameters to the {@link KryoNetEventSender}
     * @param kryoNetParams
     *          Generic parameters of the {@link KryoNetEventReceptor}
     */
    public KryoNetEventReceptor(final KryoNetConnectionParameters connectionParams,
            KryoNetParameters kryoNetParams) {
        Preconditions.checkNotNull(connectionParams);
        this.kryoNetParameters = kryoNetParams;
        this.connectionParams = connectionParams;
        stats = new EventReceptorStats(connectionParams.getId());
        listeners = new AtomicReference<>(ImmutableList.copyOf(new ArrayList<EventReceptorListener>()));
        events = new ConcurrentLinkedQueue<F2SEvent>();
        keepRunning = new AtomicBoolean(false);
        nextConnectionTime = new AtomicReference<Instant>(new Instant(0));
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#getConnectionParameters()
     */
    public KryoNetConnectionParameters getConnectionParameters() {
        return connectionParams;
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#getStats()
     */
    public EventReceptorStats getStats() {
        return stats;
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#getEvents(int)
     */
    public List<F2SEvent> getEvents(final int maxEvents) {
        final List<F2SEvent> result = new ArrayList<F2SEvent>();
        for (int i = 0; i < maxEvents; i++) {
            final F2SEvent e = events.poll();
            if (e == null) {
                break;
            }
            stats.decrEventsQueued();
            result.add(e);
        }
        LOG.trace("Collected {} event(s) (max {})", result.size(), maxEvents);
        return result;
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#getEvents()
     */
    public List<F2SEvent> getEvents() {
        return getEvents(Integer.MAX_VALUE);
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#start()
     */
    public boolean start() {
        if (keepRunning.get()) {
            LOG.warn("KryoNet client of {} already started", connectionParams.getConnectionStr());
            return true;
        }
        LOG.trace("Starting KryoNet client of {}", connectionParams.getConnectionStr());
        keepRunning.set(true);
        stats.reset();
        nextConnectionTime.set(new Instant(0));
        maintainConnectionThread = new MaintainConnection();
        maintainConnectionThread.start();
        LOG.debug("KryoNet client of {} started", connectionParams.getConnectionStr());
        return true;
    }

    /**
     * Creates the KryoNet client and attempts to connect to the KryoNet server
     * 
     * @return True if connected successfully
     */
    protected boolean connect() {
        if (client != null)
            cleanup();
        LOG.debug("KryoNet client of {} connecting...", connectionParams.getConnectionStr());
        client = new Client(connectionParams.getWriteBufferSize(), connectionParams.getObjectBufferSize(),
                new MyKryoSerialization(connectionParams.getObjectBufferSize()));
        KryoUtil.register(client.getKryo());
        client.addListener(new KryoConnectionListener());
        client.start();
        try {
            client.connect(kryoNetParameters.getConnectionTimeout(), connectionParams.getAddress(),
                    connectionParams.getPort());
            return true;
        } catch (final IOException e) {
            LOG.error("Failed to connect to the KryoNet server " + connectionParams.getConnectionStr(), e);
            return false;
        }
    }

    /**
     * @return True if connected, false otherwise
     */
    public boolean isConnected() {
        return client != null && client.isConnected();
    }

    /**
     * Cleans up the connection so it can try and reconnect
     */
    protected void cleanup() {
        try {
            LOG.debug("KryoNet client of {} closing...", connectionParams.getConnectionStr());
            if (client != null) {
                client.close();
                LOG.info("KryoNet client of {} closed", connectionParams.getConnectionStr());
            }
        } catch (Exception e) {
            LOG.error("Failed to close KryoNet client", e);
        } finally {
            client = null;
        }
    }

    /**
     * @see com.comcast.viper.flume2storm.connection.receptor.EventReceptor#stop()
     */
    public boolean stop() {
        if (!keepRunning.get()) {
            LOG.warn("KryoNet client of {} already stopped", connectionParams.getConnectionStr());
            return true;
        }
        keepRunning.set(false);
        try {
            if (maintainConnectionThread == null) {
                maintainConnectionThread.join(kryoNetParameters.getTerminationTimeout());
            }
            return true;
        } catch (final InterruptedException e) {
            LOG.warn("Failed to terminate inner thread in {} ms", kryoNetParameters.getTerminationTimeout());
            return false;
        } finally {
            maintainConnectionThread = null;
        }
    }

    protected void setNextReconnectionTime() {
        nextConnectionTime.set(Instant.now().plus(kryoNetParameters.getRetrySleepDelay()));
    }

    protected class KryoConnectionListener extends Listener {
        public KryoConnectionListener() {
        }

        /**
         * @see com.esotericsoftware.kryonet.Listener#connected(com.esotericsoftware.kryonet.Connection)
         */
        @Override
        public void connected(final Connection connection) {
            stats.setConnected();
            LOG.info("KryoNet client of {} connected", connectionParams.getConnectionStr());
            for (EventReceptorListener erListener : listeners.get()) {
                erListener.onConnection();
            }
        }

        /**
         * @see com.esotericsoftware.kryonet.Listener#disconnected(com.esotericsoftware.kryonet.Connection)
         */
        @Override
        public void disconnected(final Connection connection) {
            stats.setDisconnected();
            LOG.info("KryoNet client of {} disconnected", connectionParams.getConnectionStr());
            cleanup();
            for (EventReceptorListener erListener : listeners.get()) {
                erListener.onDisconnection();
            }
        }

        /**
         * @see com.esotericsoftware.kryonet.Listener#received(com.esotericsoftware.kryonet.Connection,
         *      java.lang.Object)
         */
        @Override
        public void received(final Connection connection, final Object object) {
            if (object instanceof F2SEvent) {
                try {
                    final F2SEvent fe = (F2SEvent) object;
                    assert fe != null;
                    LOG.trace("KryoNet client of {} received event {}", connectionParams.getConnectionStr(), fe);
                    stats.incrEventsIn();
                    if (!events.add(fe)) {
                        LOG.warn("KryoNet client of {} failed to enqueue event {}",
                                connectionParams.getConnectionStr(), fe);
                    } else {
                        stats.incrEventsQueued();
                    }
                    for (EventReceptorListener erListener : listeners.get()) {
                        erListener.onEvent(ImmutableList.of(fe));
                    }
                } catch (final ClassCastException cce) {
                    LOG.error("KryoNet client of {} received a message of unexpected type: {}",
                            connectionParams.getConnectionStr(), object.getClass());
                } catch (final Exception e) {
                    LOG.error("KryoNet client of {} failed to handle message: {}", object);
                }
                setNextReconnectionTime();
            } else if (object instanceof KeepAlive) {
                // ignoring keep alive
            } else {
                LOG.trace("KryoNet client of {} ignoring unknown message of type {}",
                        connectionParams.getConnectionStr(), object == null ? "null" : object.getClass());
            }
        }
    }

    public synchronized void addListener(EventReceptorListener listener) {
        List<EventReceptorListener> newList = new ArrayList<>(listeners.get());
        newList.add(listener);
        listeners.set(ImmutableList.copyOf(newList));
    }

    public synchronized void removeListener(EventReceptorListener listener) {
        List<EventReceptorListener> newList = new ArrayList<>(listeners.get());
        newList.remove(listener);
        listeners.set(ImmutableList.copyOf(newList));
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(connectionParams)
                .append(kryoNetParameters).append(stats).append("nextConnectionTime", nextConnectionTime)
                .toString();
    }
}