Sender.java

package org.flasby.net.mail;

import static org.flasby.net.mail.SmtpEvent.CONNECT;
import static org.flasby.net.mail.SmtpEvent.DISCONNECT;
import static org.flasby.net.mail.SmtpEvent.PROTOCOL_FAULT;
import static org.flasby.net.mail.SmtpEvent.RESP_220;
import static org.flasby.net.mail.SmtpEvent.RESP_250;
import static org.flasby.net.mail.SmtpEvent.RESP_250_HYPHON;
import static org.flasby.net.mail.SmtpEvent.RESP_5xx;
import static org.flasby.net.mail.SmtpEvent.SOCKET_FAULT;
import static org.flasby.net.mail.SmtpEvent.TIME_OUT;
import static org.flasby.net.mail.SmtpState.COMPLETED;
import static org.flasby.net.mail.SmtpState.SENDING_MAILFROM;
import static org.flasby.net.mail.SmtpState.START;
import static org.flasby.net.mail.SmtpState.WAITING_FOR_GREETING;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.function.BiConsumer;

import org.flasby.net.SocketConnector;
import org.flasby.util.statemachine.StateMachine;

/**
 * sends mail using SMTP. Potentially another implementation could send using
 * another protocol, but this would require more abstraction than I can be
 * bothered with just now.
 * 
 * Protocol is:
 * <ul>
 * <li>Open socket</li>
 * <li>Wait for greeting from server</li>
 * <li>Send EHLO with</li>
 * </ul>
 * 
 * @author steve
 *
 */
public class Sender {

	private String smtpHost;
	private int smtpPort;
	private Message msg;

	private StateMachine<SmtpState, SmtpEvent> sm = new StateMachine<>(START);

	interface SmtpAction extends BiConsumer<SmtpState, SmtpEvent>  {
	}

	private SocketConnector sock;
	private OutputStream writer;
//	private DataInputStream reader;
	private Parser parser;

	public Sender(String smtpHost, int smtpPort, Message msg) {
		this.smtpHost = smtpHost;
		this.smtpPort = smtpPort;
		this.msg = msg;

		sm.addTransition(START, WAITING_FOR_GREETING, CONNECT, openSocket);
		sm.addTransition(WAITING_FOR_GREETING, START, RESP_220, sendHello);

		sm.addTransition(START, SENDING_MAILFROM, RESP_250, registerExtension);
		sm.addTransition(START, START, RESP_250_HYPHON, registerExtension);

		sm.addTransition(SENDING_MAILFROM, COMPLETED, RESP_250, sendMailFrom);

		// We're done - this will return from the SM
		sm.addTransition(COMPLETED, COMPLETED, DISCONNECT);

		sm.addGlobalTransition(DISCONNECT, COMPLETED, closeSocket);
		sm.addGlobalTransition(SOCKET_FAULT, COMPLETED, end);
		sm.addGlobalTransition(TIME_OUT, COMPLETED, end);
		sm.addGlobalTransition(PROTOCOL_FAULT, COMPLETED, end);
		sm.addGlobalTransition(RESP_5xx, COMPLETED, end);
	}

	protected SmtpAction openSocket = (state, event) -> {
		System.out.println("Opening " + Sender.this.smtpHost + ":" + Sender.this.smtpPort);
		sock = new SocketConnector() {
			@Override
			protected void disconnected() {
				System.out.println("SocketConnector said closed");
				sm.process(DISCONNECT);
			}

			@Override
			protected void connected(BufferedInputStream inputStream, OutputStream outputStream) throws IOException {
				System.out.println("SocketConnector said: Connected");
//					Sender.this.reader = dataInputStream;
				Sender.this.writer = outputStream;
				// Once connected then invoke the parser
				// to drive the state machine
				parser = new Parser(inputStream);
				while (sm.getState() != COMPLETED) {
					sm.process(parser.getNextEvent());
				}
			}
		};
		sock.setSenderHost(Sender.this.smtpHost);
		sock.setSenderPort(Sender.this.smtpPort);
		System.out.println("Starting...");
		sock.start(false);
		System.out.println("Start Returned...");
	};

	protected SmtpAction closeSocket = (state, event) -> {
		System.out.println("Close Socket");
		sock.stop();
		System.out.println("Stopped");
	};

	protected SmtpAction end = (state, event) -> {
		sock.stop();
	};

	protected SmtpAction registerExtension = (state, event) -> {
		try {
			System.out.println("  Extension: " + parser.consumeToEol());
		} catch (IOException e) {
			processSocketFault(e);
		}
	};

	protected SmtpAction sendMailFrom = (state, event) -> {
		String ehlo = "MAIL FROM: " + Sender.this.msg.getFrom() + "\n";
		try {
			System.out.println("Consuming...");
			parser.consumeToEol();
			System.out.println("Sending: " + ehlo);
			writeln(ehlo);
		} catch (IOException e) {
			processSocketFault(e);
		}
	};

	protected SmtpAction sendHello = (state, event) -> {
		String ehlo = "EHLO " + Sender.this.msg.getFrom().substring(Sender.this.msg.getFrom().indexOf("@") + 1) + "\n";
		try {
			parser.consumeToEol();
			System.out.println("Sending: " + ehlo);
			writeln(ehlo);
		} catch (IOException e) {
			processSocketFault(e);
		}
	};

	protected void writeln(String text) throws IOException {
		writer.write(text.getBytes(Charset.forName("US-ASCII")));
		System.out.println("Sent: " + text);

	}

	public void processSocketFault(Exception ex) {
		sm.process(SOCKET_FAULT);
	}

	public SmtpState getState() {
		return sm.getState();
	}

	public void send() {
		sm.process(CONNECT);
	}

}