org.mailster.smtp.SMTPServer Maven / Gradle / Ivy
Show all versions of mailster-smtp Show documentation
package org.mailster.smtp;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.mailster.smtp.api.MessageListener;
import org.mailster.smtp.api.handler.AbstractDeliveryHandler;
import org.mailster.smtp.api.handler.DeliveryHandlerFactory;
import org.mailster.smtp.core.SMTPCodecFactory;
import org.mailster.smtp.core.SMTPConnectionHandler;
import org.mailster.smtp.core.SMTPDecoder;
import org.mailster.smtp.core.auth.AuthenticationHandlerFactory;
import org.mailster.smtp.core.commands.CommandHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Main SMTPServer class. Construct this object, set the
* hostName, port, and bind address if you wish to override the
* defaults, and call start().
*
* This class starts opens a Mina
* based listener and creates a new
* instance of the SMTPConnectionHandler class when a new connection
* comes in. The SMTPConnectionHandler then parses the incoming SMTP
* stream and hands off the processing to the CommandHandler which
* will execute the appropriate SMTP command class.
*
* Using this server is easy just use the constructor passing in a list
* of {@link MessageListener}. This is a higher, and sometimes more
* convenient level of abstraction. You can further manipulate the list
* of listeners using the methods provided by the {@link DeliveryHandlerFactory}.
*
* You can also customize the way that messages are delivered to the
* listeners by writing your own delivery handler implementation by extending
* {@link AbstractDeliveryHandler} and registering it with the server using
* the following code line :
*
* server.getDeliveryHandlerFactory().
* setDeliveryHandlerImplClass(Class<? extends AbstractDeliveryHandler> impl);
*
* You can always add, remove listeners by using the provided
* methods of the {@link DeliveryHandlerFactory}.
*
* In neither case is the SMTP server (this library) responsible
* for deciding what recipients to accept or what to do with the
* incoming data. That is left to you.
*
* @author De Oliveira Edouard <[email protected]>
*
* NB: some code comes from a fork from the SubethaSMTP project.
*/
public class SMTPServer {
private static final Logger LOG = LoggerFactory.getLogger(SMTPServer.class);
/**
* default to all interfaces
*/
private InetAddress bindAddress = null;
/**
* default to 25
*/
private int port = 25;
private DeliveryHandlerFactory deliveryHandlerFactory;
private CommandHandler commandHandler;
private SMTPConnectionHandler handler;
private SocketAcceptor acceptor;
private ExecutorService executor;
private SMTPCodecFactory codecFactory;
private boolean running = false;
private boolean shutdowned = false;
/**
* The server configuration.
*/
private SMTPServerConfig config = new SMTPServerConfig();
public SMTPServer() {
List listeners = Collections.emptyList();
initInstance(listeners);
}
public SMTPServer(MessageListener listener) {
List listeners = new ArrayList<>(1);
listeners.add(listener);
initInstance(listeners);
}
public SMTPServer(Collection listeners) {
initInstance(listeners);
}
private void initInstance(Collection listeners) {
this.deliveryHandlerFactory = new DeliveryHandlerFactory(listeners);
this.commandHandler = new CommandHandler();
initService();
}
/**
* Initializes the runtime service.
*/
private void initService() {
try {
IoBuffer.setUseDirectBuffer(false);
acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors() + 1);
acceptor.getSessionConfig().setReuseAddress(true);
var chain = acceptor.getFilterChain();
if (LOG.isTraceEnabled()) {
chain.addLast("logger", new LoggingFilter());
}
codecFactory = new SMTPCodecFactory(config);
chain.addLast("codec", new ProtocolCodecFilter(codecFactory));
executor = Executors.newCachedThreadPool(new ThreadFactory() {
int sequence;
@Override
public Thread newThread(Runnable r) {
sequence += 1;
return new Thread(r, "MailsterSMTP Thread " + sequence);
}
});
chain.addLast("threadPool", new ExecutorFilter(executor));
handler = new SMTPConnectionHandler(getConfig(), getCommandHandler(), getDeliveryHandlerFactory());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Call this method to get things rolling after instantiating the
* SMTPServer.
*/
public synchronized void start() {
if (running) {
LOG.info("SMTP server is already started.");
return;
}
if (shutdowned) {
throw new RuntimeException("Error: server has been shutdown previously");
}
// Read smtp decoder configuration options
((SMTPDecoder) codecFactory.getDecoder(null)).
setup(getConfig().getCharset(), getConfig().getDataDeferredSize());
InetSocketAddress isa;
if (this.bindAddress == null) {
isa = new InetSocketAddress(this.port);
} else {
isa = new InetSocketAddress(this.bindAddress, this.port);
}
acceptor.setBacklog(config.getBacklog());
acceptor.setHandler(handler);
try {
acceptor.bind(isa);
running = true;
LOG.info("SMTP server started ...");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Stops the server by unbinding server socket. To really clean
* things out, one must call {@link #shutdown()}.
*/
public synchronized void stop() {
try {
try {
acceptor.unbind();
} catch (Exception e) {
e.printStackTrace();
}
LOG.info("SMTP server stopped.");
} finally {
running = false;
}
}
/**
* Shut things down gracefully. Please pay attention to the fact
* that a shutdown implies that the server would fail to restart
* because som internal resources have been freed.
*
* You can directly call shutdown() if you do not intend to restart
* it later. Calling start() after shutdown() will throw a
* {@link RuntimeException}.
*/
public synchronized void shutdown() {
try {
LOG.info("SMTP server shutting down...");
if (isRunning()) {
stop();
}
try {
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
shutdowned = true;
LOG.info("SMTP server shutdown complete.");
} finally {
running = false;
}
}
/**
* Is the server running after start() has been called?
*/
public synchronized boolean isRunning() {
return this.running;
}
/**
* Returns the bind address. Null means all interfaces.
*/
public InetAddress getBindAddress() {
return this.bindAddress;
}
/**
* Sets the bind address. Null means all interfaces.
*/
public void setBindAddress(InetAddress bindAddress) {
this.bindAddress = bindAddress;
}
/**
* Returns the port the server is running on.
*/
public int getPort() {
return this.port;
}
/**
* Sets the port the server will run on.
*/
public void setPort(int port) {
this.port = port;
}
public void setAuthenticationHandlerFactory(AuthenticationHandlerFactory factory) {
this.deliveryHandlerFactory.setAuthenticationHandlerFactory(factory);
}
/**
* All smtp data is routed through the handler.
*/
public DeliveryHandlerFactory getDeliveryHandlerFactory() {
return this.deliveryHandlerFactory;
}
/**
* The CommandHandler manages handling the SMTP commands
* such as QUIT, MAIL, RCPT, DATA, etc.
*
* @return An instance of CommandHandler
*/
public CommandHandler getCommandHandler() {
return this.commandHandler;
}
/**
* Returns the server configuration.
*/
public SMTPServerConfig getConfig() {
return config;
}
}