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

com.lafaspot.pop.session.PopSession Maven / Gradle / Ivy

The newest version!
/**
 *
 */
package com.lafaspot.pop.session;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Nonnull;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

import com.lafaspot.logfast.logging.Logger;
import com.lafaspot.pop.command.PopCommand;
import com.lafaspot.pop.command.PopCommandResponse;
import com.lafaspot.pop.exception.PopException;
import com.lafaspot.pop.exception.PopException.Type;
import com.lafaspot.pop.netty.PopMessageDecoder;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

/**
 * The PopSession object, used to conenct and send commands to the server.
 *
 * @author kraman
 *
 */
public class PopSession {

    /** Future for the current command being executed. */
    // private PopFuture currentCommandFuture;

    /** State of the sesson. */
    private final AtomicReference stateRef;

    /** Netty bootstrap object. */
    private final Bootstrap bootstrap;

    /** The logger object. */
    private final Logger logger;

    /** The Netty session object. */
    private Channel sessionChannel;

    /** current list of commands being processed. */
    private final LinkedHashSet commandList = new LinkedHashSet();

    /** The SslContext object. */
    private final SslContext sslContext;
    /** Max line length. */
    private static final int MAX_LINE_LENGTH = 8192;

    /** The string identifier for ssl handler. */
    public static final String SSL_HANDLER = "sslHandler";

    /** The string identifier for inactivity handler. */
    public static final String INACTIVITY_HANDLER = "inactivityHandler";

    /** The string identifier for delimiter. */
    public static final String DELIMITER = "delimiter";

    /** The string identifier for encoder. */
    public static final String ENCODER = "encoder";

    /** The string identifier for decoder. */
    public static final String DECODER = "decoder";

    /** The string identifier for pop handler. */
    public static final String POP_HANDLER = "popHandler";

    /**
     * Constructor for PopSession, used to communicate with a POP server.
     *
     * @param sslContext the ssl context object
     * @param bootstrap the Netty bootstrap object
     * @param logger the logger object
     */
    public PopSession(@Nonnull final SslContext sslContext, @Nonnull final Bootstrap bootstrap, @Nonnull final Logger logger) {
        this.sslContext = sslContext;
        this.bootstrap = bootstrap;
        this.logger = logger;
        this.stateRef = new AtomicReference<>(State.NULL);
    }

    /**
     * Returns the channel that the pop client is using to connect to the remote server.
     *
     * @return the channel used to connect to the remote server or null if the session is not connected.
     */
    public Channel getChannel() {
        return sessionChannel;
    }

    /**
     * Connect to the specified POP server with the autoread option.
     *
     * @param server the server to connect to
     * @param port to connect to
     * @param connectTimeout timeout value
     * @param inactivityTimeout timeout value
     * @return future object for connect
     * @throws PopException on failure
     */
    public PopFuture connect(@Nonnull final String server, final int port, final int connectTimeout, final int inactivityTimeout)
            throws PopException {
        return connect(server, port, connectTimeout, inactivityTimeout, Arrays.asList(new String[] {}));
    }

    /**
     * Connect to the specified POP server with the autoread option.
     *
     * @param server the server to connect to
     * @param port to connect to
     * @param connectTimeout timeout value
     * @param inactivityTimeout timeout value
     * @param sniList list of server name indicators
     * @return future object for connect
     * @throws PopException on failure
     */

    public PopFuture connect(@Nonnull final String server, final int port, final int connectTimeout, final int inactivityTimeout,
            @Nonnull final List sniList) throws PopException {
        logger.debug(" +++ connect to  " + server, null);

        if (!stateRef.compareAndSet(State.NULL, State.CONNECTED)) {
            throw new PopException(Type.INVALID_STATE);
        }

        final List serverList = new ArrayList();
        if (null != sniList && !sniList.isEmpty()) {
            try {
                for (final String sni : sniList) {
                    serverList.add(new SNIHostName(sni));
                }
            } catch (IllegalArgumentException iae) {
                throw new PopException(PopException.Type.INVALID_ARGUMENTS);
            }
        }

        final PopSession thisSession = this;
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(final SocketChannel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                if (!serverList.isEmpty()) {
                    final SSLParameters params = new SSLParameters();
                    params.setServerNames(serverList);
                    final SSLEngine engine = SslContextBuilder.forClient().build().newEngine(ch.alloc());
                    engine.setSSLParameters(params);
                    pipeline.addLast(SSL_HANDLER, new SslHandler(engine));
                } else {
                    pipeline.addLast(SSL_HANDLER, sslContext.newHandler(ch.alloc(), server, port));
                }

                pipeline.addLast(INACTIVITY_HANDLER, new PopInactivityHandler(thisSession, inactivityTimeout, logger));
                pipeline.addLast(DELIMITER, new DelimiterBasedFrameDecoder(MAX_LINE_LENGTH, Delimiters.lineDelimiter()));
                pipeline.addLast(DECODER, new StringDecoder());
                pipeline.addLast(ENCODER, new StringEncoder());
                pipeline.addLast(POP_HANDLER, new PopMessageDecoder(thisSession, logger));
            }
        });

        final PopCommand cmd = new PopCommand(PopCommand.Type.INVALID_POP_COMMAND_CONNECT);
        ChannelFuture nettyConnectFuture;
        PopFuture connectFuture;
        nettyConnectFuture = bootstrap.connect(server, port);
        sessionChannel = nettyConnectFuture.channel();
        connectFuture = new PopFuture(nettyConnectFuture);
        cmd.setCommandFuture(connectFuture);
        commandList.add(cmd);


        // handle connect done
        nettyConnectFuture.addListener(new GenericFutureListener>() {
            @Override
            public void operationComplete(final Future future) throws Exception {
                logger.debug("=== channel conneced " + commandList.size(), null);
                if (commandList.size() > 0) {
                    final PopCommand command = (PopCommand) commandList.toArray()[0];
                    if (command.getType().equals(PopCommand.Type.INVALID_POP_COMMAND_CONNECT)) {
                        // wait for +OK from server to declare command complete
                        //commandList.remove(command);
                    }
                    final PopFuture currentCommandFuture = command.getCommandFuture();
                    if (null != currentCommandFuture) {
                        logger.debug("+++ connect marking done  " + currentCommandFuture, null);
                        // CONNECT action is not done until we receive the first +OK response from server
                        //currentCommandFuture.done(new PopCommandResponse(command));
                    } else {
                        logger.debug("+++ connect future is null ", null);
                    }
                }
            }
        });


        // close handling
        nettyConnectFuture.channel().closeFuture().addListener(new GenericFutureListener>() {
            @Override
            public void operationComplete(final Future future) throws Exception {
                logger.debug("+++ channel disconneced " + commandList.size(), null);
                if (commandList.size() > 0) {
                    final PopCommand command = (PopCommand) commandList.toArray()[0];
                    final PopFuture currentCommandFuture = command.getCommandFuture();
                    if (null != currentCommandFuture) {
                        logger.debug("+++ disc marking done  " + currentCommandFuture, null);
                        currentCommandFuture.done(new PopException(PopException.Type.CHANNEL_DISCONNECTED));
                    } else {
                        logger.debug("+++ disc future is null ", null);
                    }
                }
            }
        });
        // nettyConnectFuture.sync();
        return connectFuture;
    }

    /**
     * Send a POP command to the server.
     *
     * @param command the command to send to server
     * @return the future object for this command
     * @throws PopException on failure
     */
    public PopFuture execute(@Nonnull final PopCommand command) throws PopException {

        if (sessionChannel == null || !sessionChannel.isActive()) {
            throw new PopException(PopException.Type.CHANNEL_NOT_CONNECTED);
        }

        /*
        if (!commandList.isEmpty()) {
            // don't support pipelining
            throw new PopException(PopException.Type.INVALID_STATE);
        }
        */

        final StringBuilder commandToWrite = new StringBuilder();
        commandToWrite.append(command.getCommandLine());

        final Future writeFuture = sessionChannel.writeAndFlush(commandToWrite.toString());
        final PopFuture currentCommandFuture = new PopFuture(writeFuture);

        command.setCommandFuture(currentCommandFuture);
        commandList.add(command);
        return currentCommandFuture;
    }

    /**
     * Disconnect the session, close session and cleanup.
     *
     * @return the future object for disconnect
     * @throws PopException on failure
     */
    public PopFuture disconnect() throws PopException {
        final State state = stateRef.get();
        if (state != State.CONNECTED) {
            throw new PopException(PopException.Type.INVALID_STATE);
        }
        if (stateRef.compareAndSet(state, State.NULL)) {
            Future f = sessionChannel.disconnect();
            PopFuture disconnectFuture = new PopFuture(f);
            sessionChannel = null;
            return disconnectFuture;
        }
        throw new PopException(PopException.Type.INVALID_STATE);
    }

    /**
     * Callback from netty on channel inactivity.
     */
    public void onTimeout() {
        logger.debug("**channel timeout** TH " + Thread.currentThread().getId(), null);

        if (commandList.isEmpty()) {
            return;
        }
        final PopCommand command = (PopCommand) commandList.toArray()[0];
        final PopFuture currentCommandFuture = command.getCommandFuture();
        if (null == currentCommandFuture) {
            return;
        }
        currentCommandFuture.done(new PopException(PopException.Type.CHANNEL_DISCONNECTED));

    }

    /**
     * Called when response message is being received from the server. Delimiter is \r\n.
     *
     * @param line the response line
     */
    public void onMessage(final String line) {
        if (commandList.isEmpty()) {
            // something went wrong, shutdown and bail out
            try {
                disconnect();
            } catch (PopException e) {
                // ignore
            }
            return;
        }

        final PopCommand command = (PopCommand) commandList.toArray()[0];
        final PopFuture currentCommandFuture = command.getCommandFuture();
        if (null == currentCommandFuture) {
            // fatal
            // something went wrong, shutdown and bail out
            try {
                disconnect();
            } catch (PopException e) {
                // ignore
            }
            return;
        }

        command.getResponse().parse(line);
        if (command.getResponse().parseComplete()) {
            commandList.remove(command);
            currentCommandFuture.done(command.getResponse());
        }
    }


    /**
     * States of PopSession.
     *
     * @author kraman
     *
     */
    public enum State {
        /** Null session not connected. */
        NULL,
        /** Session is connected, ready to accept commands. */
        CONNECTED;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy