package mireka.transmission.immediate;
import java.util.Arrays;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
import mireka.smtp.SendException;
import mireka.smtp.client.BackendServer;
import mireka.smtp.client.SmtpClient;
import mireka.transmission.Mail;
import mireka.transmission.immediate.host.MailToHostTransmitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* IndirectImmediateSender synchronously sends all mails through other SMTP
* servers specified in the configuration, typically to a smarthost, instead of
* sending the mail directly to the SMTP servers of the recipients. It tries all
* listed smarthosts until a working one is found. The smarthost will in turn
* transmit the mail to remote domains. This is useful for example if a network
* is behind a dynamic IP address, considering that dynamic IP addresses are
* frequently rejected by SMTP servers.
* <p>
* If a smart host name resolves to more than one IP addresses, than only the
* first one is used.
* <p>
* Instead of specifying a single smarthost, an Upstream with more servers can
* also be supplied and Mireka will distribute outgoing mails like a simple load
* balancer.
*/
@NotThreadSafe
public class IndirectImmediateSender implements ImmediateSender {
private final Logger logger = LoggerFactory
.getLogger(IndirectImmediateSender.class);
private MailToHostTransmitter mailToHostTransmitter;
private Upstream upstream = new Upstream();
@Override
public boolean singleDomainOnly() {
return false;
}
/**
* Transmits mail to a smart host.
*
* @throws PostponeException
* if transmission to all of the hosts must be postponed,
* because all of them are assumed to be busy at this moment.
*/
@Override
public void send(Mail mail) throws SendException,
RecipientsWereRejectedException, IllegalArgumentException,
PostponeException {
// a PostponeException does not prevent successful delivery using
// another host, but it must be saved so if there are no more hosts then
// this exception instance will be rethrown.
PostponeException lastPostponeException = null;
// if there is a host which failed, but which should be retried later,
// then a following unrecoverable DNS exception on another host may
// not prevent delivery, so this temporary exception will be returned
SendException lastRetryableException = null;
// an unrecoverable DNS exception may not prevent delivery (to another
// host), so the function will continue, but it must be
// saved, because maybe there is no more host.
SendException lastUnrecoverableDnsException = null;
List<BackendServer> servers = upstream.orderedServerList();
logger.debug("Trying backends in this order: {}", servers);
for (BackendServer server : servers) {
SmtpClient client;
try {
client = server.createClient();
} catch (SendException e) {
if (e.errorStatus().shouldRetry())
lastRetryableException = e;
else
lastUnrecoverableDnsException = e;
logger.debug("Looking up address of MTA {} failed, continuing "
+ "with the next MTA if one is available: {}", server,
e.getMessage());
continue;
}
try {
mailToHostTransmitter.transmit(mail, client);
return;
} catch (PostponeException e) {
lastPostponeException = e;
logger.debug("Sending to SMTP host {} must be postponed, "
+ "continuing with the next "
+ "smart host if one is available: {}",
client.getMtaAddress(), e.getMessage());
} catch (SendException e) {
if (e.errorStatus().shouldRetry()) {
// lastSendException = e;
lastRetryableException = e;
logger.debug("Sending to SMTP host {} failed, "
+ "continuing with the next "
+ "smart host if one is available: {}",
client.getMtaAddress(), e.getMessage());
} else {
throw e;
}
}
}
// at this point it is known that the transmission was not successful
if (lastRetryableException != null)
throw lastRetryableException;
if (lastPostponeException != null) {
// there is at least one host successfully found in DNS but have not
// tried
throw lastPostponeException;
}
if (lastUnrecoverableDnsException == null)
throw new RuntimeException(); // impossible, but prevents warning
// an unrecoverable DNS exception
throw lastUnrecoverableDnsException;
}
/** @x.category GETSET **/
public MailToHostTransmitter getMailToHostTransmitter() {
return mailToHostTransmitter;
}
/** @x.category GETSET **/
public void setMailToHostTransmitter(
MailToHostTransmitter mailToHostTransmitter) {
this.mailToHostTransmitter = mailToHostTransmitter;
}
/**
* Sets the upstream to the supplied single server.
*
* @x.category GETSET
**/
public void setBackendServer(BackendServer server) {
upstream.setServers(Arrays.asList(server));
}
/** @x.category GETSET **/
public Upstream getUpstream() {
return upstream;
}
/** @x.category GETSET **/
public void setUpstream(Upstream upstream) {
this.upstream = upstream;
}
}