com.ok2c.lightmtp.impl.protocol.PipeliningReceiveEnvelopCodec.java Source code

Java tutorial

Introduction

Here is the source code for com.ok2c.lightmtp.impl.protocol.PipeliningReceiveEnvelopCodec.java

Source

/*
 * 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.ok2c.lightmtp.impl.protocol;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.SessionInputBuffer;
import org.apache.http.nio.reactor.SessionOutputBuffer;
import org.apache.http.util.Args;

import com.ok2c.lightmtp.SMTPCode;
import com.ok2c.lightmtp.SMTPCodes;
import com.ok2c.lightmtp.SMTPCommand;
import com.ok2c.lightmtp.SMTPErrorException;
import com.ok2c.lightmtp.SMTPProtocolException;
import com.ok2c.lightmtp.SMTPReply;
import com.ok2c.lightmtp.impl.protocol.cmd.SimpleAction;
import com.ok2c.lightmtp.message.SMTPCommandParser;
import com.ok2c.lightmtp.message.SMTPMessageParser;
import com.ok2c.lightmtp.message.SMTPMessageWriter;
import com.ok2c.lightmtp.message.SMTPReplyWriter;
import com.ok2c.lightmtp.protocol.Action;
import com.ok2c.lightmtp.protocol.ProtocolCodec;
import com.ok2c.lightmtp.protocol.ProtocolCodecs;
import com.ok2c.lightmtp.protocol.ProtocolHandler;

public class PipeliningReceiveEnvelopCodec implements ProtocolCodec<ServerState> {

    private final SMTPBuffers iobuffers;
    private final ProtocolHandler<ServerState> commandHandler;
    private final SMTPMessageParser<SMTPCommand> parser;
    private final SMTPMessageWriter<SMTPReply> writer;
    private final Queue<Action<ServerState>> pendingActions;

    private Future<SMTPReply> actionFuture;
    private boolean completed;

    public PipeliningReceiveEnvelopCodec(final SMTPBuffers iobuffers,
            final ProtocolHandler<ServerState> commandHandler) {
        super();
        Args.notNull(iobuffers, "IO buffers");
        Args.notNull(commandHandler, "Command handler");
        this.iobuffers = iobuffers;
        this.commandHandler = commandHandler;
        this.parser = new SMTPCommandParser();
        this.writer = new SMTPReplyWriter(true);
        this.pendingActions = new LinkedList<Action<ServerState>>();
        this.completed = false;
    }

    @Override
    public void cleanUp() {
    }

    @Override
    public void reset(final IOSession iosession, final ServerState sessionState)
            throws IOException, SMTPProtocolException {
        this.parser.reset();
        this.writer.reset();
        this.pendingActions.clear();
        this.actionFuture = null;
        this.completed = false;
    }

    private SMTPReply getReply(final Future<SMTPReply> future) {
        try {
            return future.get();
        } catch (ExecutionException ex) {
            Throwable cause = ex.getCause();
            if (cause == null) {
                cause = ex;
            }
            return new SMTPReply(SMTPCodes.ERR_PERM_TRX_FAILED, new SMTPCode(5, 3, 0), cause.getMessage());
        } catch (InterruptedException ex) {
            return new SMTPReply(SMTPCodes.ERR_PERM_TRX_FAILED, new SMTPCode(5, 3, 0), ex.getMessage());
        }
    }

    @Override
    public void produceData(final IOSession iosession, final ServerState sessionState)
            throws IOException, SMTPProtocolException {
        Args.notNull(iosession, "IO session");
        Args.notNull(sessionState, "Session state");

        SessionOutputBuffer buf = this.iobuffers.getOutbuf();

        synchronized (sessionState) {

            if (this.actionFuture != null) {
                SMTPReply reply = getReply(this.actionFuture);
                this.actionFuture = null;
                this.writer.write(reply, buf);
            }

            if (this.actionFuture == null) {
                while (!this.pendingActions.isEmpty()) {
                    Action<ServerState> action = this.pendingActions.remove();
                    Future<SMTPReply> future = action.execute(sessionState,
                            new OutputTrigger<SMTPReply>(sessionState, iosession));
                    if (future.isDone()) {
                        SMTPReply reply = getReply(future);
                        this.writer.write(reply, buf);
                    } else {
                        this.actionFuture = future;
                        break;
                    }
                }
            }

            if (buf.hasData()) {
                buf.flush(iosession.channel());
            }
            if (!buf.hasData()) {
                if (sessionState.getDataType() != null) {
                    this.completed = true;
                }
                if (sessionState.isTerminated()) {
                    iosession.close();
                } else {
                    iosession.clearEvent(SelectionKey.OP_WRITE);
                }
            }
        }
    }

    @Override
    public void consumeData(final IOSession iosession, final ServerState sessionState)
            throws IOException, SMTPProtocolException {
        Args.notNull(iosession, "IO session");
        Args.notNull(sessionState, "Session state");

        SessionInputBuffer buf = this.iobuffers.getInbuf();

        synchronized (sessionState) {
            for (;;) {
                int bytesRead = buf.fill(iosession.channel());
                try {
                    SMTPCommand command = this.parser.parse(buf, bytesRead == -1);
                    if (command == null) {
                        if (bytesRead == -1 && !sessionState.isTerminated() && this.pendingActions.isEmpty()) {
                            throw new UnexpectedEndOfStreamException();
                        } else {
                            break;
                        }
                    }
                    Action<ServerState> action = this.commandHandler.handle(command);
                    this.pendingActions.add(action);
                } catch (SMTPErrorException ex) {
                    SMTPReply reply = new SMTPReply(ex.getCode(), ex.getEnhancedCode(), ex.getMessage());
                    this.pendingActions.add(new SimpleAction(reply));
                } catch (SMTPProtocolException ex) {
                    SMTPReply reply = new SMTPReply(SMTPCodes.ERR_PERM_SYNTAX_ERR_COMMAND, new SMTPCode(5, 3, 0),
                            ex.getMessage());
                    this.pendingActions.add(new SimpleAction(reply));
                }
            }

            if (!this.pendingActions.isEmpty()) {
                iosession.setEvent(SelectionKey.OP_WRITE);
            }
        }
    }

    @Override
    public boolean isCompleted() {
        return this.completed;
    }

    @Override
    public String next(final ProtocolCodecs<ServerState> codecs, final ServerState sessionState) {
        if (isCompleted()) {
            if (sessionState.isTerminated()) {
                return ProtocolState.QUIT.name();
            }
            return ProtocolState.DATA.name();
        } else {
            return null;
        }
    }

}