[mireka] r191 committed - refactoring: ImmediateSender become a service API, with a single imple...

3 views
Skip to first unread message

mir...@googlecode.com

unread,
May 5, 2013, 10:38:07 AM5/5/13
to mir...@googlegroups.com
Revision: 191
Author: hont...@flyordie.com
Date: Sun May 5 07:37:27 2013
Log: refactoring: ImmediateSender become a service API, with a single
implementation, in preparation for adding null mailer functionality.
http://code.google.com/p/mireka/source/detail?r=191

Added:
/trunk/src/mireka/transmission/immediate/direct
/trunk/src/mireka/transmission/immediate/direct/DirectImmediateSender.java

/trunk/src/mireka/transmission/immediate/direct/DirectImmediateSenderFactory.java
/trunk/src/mireka/transmission/immediate/direct/package-info.java
/trunk/src/mireka/transmission/immediate/host
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitter.java

/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitterFactory.java

/trunk/src/mireka/transmission/immediate/host/OutgoingConnectionsRegistry.java

/trunk/src/mireka/transmission/immediate/host/SmartClientOutputStreamAdapter.java
/trunk/src/mireka/transmission/immediate/host/package-info.java
/trunk/test/mireka/transmission/immediate/ResponseFactoryTest.java
/trunk/test/mireka/transmission/immediate/direct
/trunk/test/mireka/transmission/immediate/direct/ImmediateSenderTest.java
Deleted:
/trunk/src/mireka/transmission/immediate/MailToHostTransmitter.java
/trunk/src/mireka/transmission/immediate/MailToHostTransmitterFactory.java
/trunk/src/mireka/transmission/immediate/OutgoingConnectionsRegistry.java

/trunk/src/mireka/transmission/immediate/SmartClientOutputStreamAdapter.java
/trunk/test/mireka/submission/ResponseFactoryTest.java
/trunk/test/mireka/transmission/immediate/ImmediateSenderTest.java
Modified:
/trunk/setup/conf/submission/queues.js
/trunk/setup/lib/configuration.js
/trunk/src/mireka/smtp/EnhancedStatus.java
/trunk/src/mireka/transmission/immediate/ImmediateSender.java
/trunk/src/mireka/transmission/immediate/ImmediateSenderFactory.java
/trunk/src/mireka/transmission/immediate/RecipientRejection.java

/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
/trunk/src/mireka/transmission/immediate/ResponseParser.java
/trunk/src/mireka/transmission/immediate/package-info.java
/trunk/src/mireka/transmission/queue/MailProcessor.java
/trunk/src/mireka/transmission/queuing/QueuingTransmitter.java
/trunk/test/mireka/transmission/immediate/MailToHostTransmitterTest.java

/trunk/test/mireka/transmission/immediate/OutgoingConnectionsRegistryTest.java
Replaced:
/trunk/src/mireka/transmission/immediate/RemoteMta.java

=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/direct/DirectImmediateSender.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,196 @@
+package mireka.transmission.immediate.direct;
+
+import java.net.InetAddress;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import mireka.address.AddressLiteral;
+import mireka.address.Domain;
+import mireka.address.DomainPart;
+import mireka.address.Recipient;
+import mireka.address.RemotePart;
+import mireka.address.RemotePartContainingRecipient;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.ImmediateSender;
+import mireka.transmission.immediate.PostponeException;
+import mireka.transmission.immediate.RecipientsWereRejectedException;
+import mireka.transmission.immediate.RemoteMta;
+import mireka.transmission.immediate.SendException;
+import mireka.transmission.immediate.dns.AddressLookupFactory;
+import mireka.transmission.immediate.dns.MxLookupFactory;
+import mireka.transmission.immediate.host.MailToHostTransmitterFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xbill.DNS.Name;
+
+/**
+ * DirectImmediateSender synchronously sends a mail to a domain, which may
+ * include attempting delivery to more than one MX hosts of the domain
until a
+ * working one is found. If it cannot transmit the mail to any of the MX
hosts
+ * of the domain, then it throws an exception, it does not retry later.
+ * <p>
+ * TODO: if a recipient is rejected because of a transient failure, then it
+ * should be retried on another host.
+ */
+@NotThreadSafe
+public class DirectImmediateSender implements ImmediateSender {
+ private final Logger logger = LoggerFactory
+ .getLogger(DirectImmediateSender.class);
+ private final MxLookupFactory mxLookupFactory;
+ private final AddressLookupFactory addressLookupFactory;
+ private final MailToHostTransmitterFactory
mailToHostTransmitterFactory;
+ private Mail mail;
+
+ DirectImmediateSender(MxLookupFactory mxLookupFactory,
+ AddressLookupFactory addressLookupFactory,
+ MailToHostTransmitterFactory mailToHostTransmitterFactory) {
+ this.mxLookupFactory = mxLookupFactory;
+ this.addressLookupFactory = addressLookupFactory;
+ this.mailToHostTransmitterFactory = mailToHostTransmitterFactory;
+ }
+
+ /**
+ * Transmits mail to a single domain.
+ *
+ * @throws IllegalArgumentException
+ * if the domains of the recipients are not the same, or
if the
+ * recipient is the special global postmaster address,
which has
+ * no absolute domain.
+ * @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 {
+ this.mail = mail;
+ RemotePart remotePart = commonRecipientRemotePart();
+ if (remotePart instanceof AddressLiteral) {
+ AddressLiteral addressLiteral = (AddressLiteral) remotePart;
+ sendToAddressLiteral(addressLiteral);
+ } else if (remotePart instanceof DomainPart) {
+ Domain domain = ((DomainPart) remotePart).domain;
+ sendToDomain(domain);
+ } else {
+ throw new RuntimeException();
+ }
+ }
+
+ private RemotePart commonRecipientRemotePart()
+ throws IllegalArgumentException {
+ RemotePart result = null;
+ for (Recipient recipient : mail.recipients) {
+ if (!(recipient instanceof RemotePartContainingRecipient))
+ throw new IllegalArgumentException(
+ "Cannot send mail to non-remote address: " +
recipient);
+ RemotePart remotePart =
+ ((RemotePartContainingRecipient)
recipient).getMailbox()
+ .getRemotePart();
+ if (result == null) {
+ result = remotePart;
+ } else {
+ if (!result.equals(remotePart))
+ throw new IllegalArgumentException(
+ "Recipients are expected to belong to the same
domain. "
+ + " Recipient list contains both " +
result
+ + " and " + remotePart);
+ }
+ }
+ if (result == null)
+ throw new IllegalArgumentException("recipient list is empty");
+ return result;
+ }
+
+ private void sendToAddressLiteral(AddressLiteral target)
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ RemoteMta remoteMta =
+ new RemoteMta(target.toString(), target.inetAddress()
+ .getHostAddress());
+ mailToHostTransmitterFactory.create(remoteMta).transmit(mail,
+ target.inetAddress());
+ }
+
+ /**
+ * Queries MX hosts of the domain and tries to transmit to the hosts
until
+ * it is successful or no more hosts remain.
+ *
+ * @throws PostponeException
+ * if transmission to all of the hosts must be postponed,
+ * because all of them are assumed to be busy at this
moment.
+ */
+ private void sendToDomain(Domain domain) throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ Name[] mxNames = mxLookupFactory.create(domain).queryMxTargets();
+
+ // 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 MX 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
+ // MX host of the domain), so the function will continue, but it
must be
+ // saved, because maybe there is no more host.
+ SendException lastUnrecoverableDnsException = null;
+ for (Name name : mxNames) {
+ InetAddress[] addresses;
+ try {
+ addresses =
addressLookupFactory.create(name).queryAddresses();
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry())
+ lastRetryableException = e;
+ else
+ lastUnrecoverableDnsException = e;
+ logger.debug("Looking up address of MX host " + name
+ + " failed, continuing with the next MX host "
+ + "if one is available: ", e.getMessage());
+ continue;
+ }
+
+ try {
+ for (InetAddress hostAddress : addresses) {
+ RemoteMta remoteMta =
+ new RemoteMta(name.toString(),
+ hostAddress.getHostAddress());
+
mailToHostTransmitterFactory.create(remoteMta).transmit(
+ mail, hostAddress);
+ return;
+ }
+ } catch (PostponeException e) {
+ lastPostponeException = e;
+ logger.debug("Sending to SMTP host " + name
+ + " must be postponed, continuing with the next "
+ + "MX host if one is available: " +
e.getMessage());
+ } catch (SendException e) {
+ if (e.errorStatus().shouldRetry()) {
+ // lastSendException = e;
+ lastRetryableException = e;
+ logger.debug("Sending to SMTP host " + name
+ + " failed, continuing with the next "
+ + "MX host if one is available: ",
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;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/direct/DirectImmediateSenderFactory.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,34 @@
+package mireka.transmission.immediate.direct;
+
+import mireka.transmission.immediate.ImmediateSender;
+import mireka.transmission.immediate.ImmediateSenderFactory;
+import mireka.transmission.immediate.dns.AddressLookupFactory;
+import mireka.transmission.immediate.dns.MxLookupFactory;
+import mireka.transmission.immediate.host.MailToHostTransmitterFactory;
+
+/**
+ * DirectImmediateSenderFactory is an ImmediateSenderFactory which creates
+ * DirectImmediateSender instances.
+ */
+public class DirectImmediateSenderFactory implements
ImmediateSenderFactory {
+ private MailToHostTransmitterFactory mailToHostTransmitterFactory;
+
+ @Override
+ public ImmediateSender create() {
+ return new DirectImmediateSender(new MxLookupFactory(),
+ new AddressLookupFactory(), mailToHostTransmitterFactory);
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setMailToHostTransmitterFactory(
+ MailToHostTransmitterFactory mailToHostTransmitterFactory) {
+ this.mailToHostTransmitterFactory = mailToHostTransmitterFactory;
+ }
+
+ @Override
+ public boolean singleDomainOnly() {
+ return true;
+ }
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/transmission/immediate/direct/package-info.java Sun
May 5 07:37:27 2013
@@ -0,0 +1,10 @@
+/**
+ * An ImmediateSender implementation package, which makes a single
synchronous
+ * attempt to directly transmits mail to the SMTP servers of the remote
domain.
+ * The remote domain is specified by the remote part of the recipient
addresses,
+ * which must be the same for all recipients in case of this
implementation. The
+ * receiving SMTP servers are usually specified by the MX records of the
remote
+ * domain, except if the remote part is a literal address, or the domain
has an
+ * implicit MX record only.
+ */
+package mireka.transmission.immediate.direct;
=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitter.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,129 @@
+package mireka.transmission.immediate.host;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import mireka.address.Recipient;
+import mireka.smtp.ClientFactory;
+import mireka.smtp.EnhancedStatus;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.PostponeException;
+import mireka.transmission.immediate.RecipientRejection;
+import mireka.transmission.immediate.RecipientsWereRejectedException;
+import mireka.transmission.immediate.RemoteMta;
+import mireka.transmission.immediate.RemoteMtaErrorResponseException;
+import mireka.transmission.immediate.SendException;
+import mireka.transmission.queuing.LogIdFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.subethamail.smtp.client.SMTPException;
+import org.subethamail.smtp.client.SmartClient;
+
+/**
+ * MailToHostTransmitter transmits a mail to a specific host specified by
its IP
+ * address.
+ */
+@NotThreadSafe
+public class MailToHostTransmitter {
+ private final Logger logger = LoggerFactory
+ .getLogger(MailToHostTransmitter.class);
+ private final ClientFactory clientFactory;
+ private final OutgoingConnectionsRegistry outgoingConnectionsRegistry;
+ private final LogIdFactory logIdFactory;
+ private final RemoteMta remoteMta;
+ private Mail mail;
+
+ public MailToHostTransmitter(ClientFactory clientFactory,
+ OutgoingConnectionsRegistry outgoingConnectionsRegistry,
+ LogIdFactory logIdFactory, RemoteMta remoteMta) {
+ this.clientFactory = clientFactory;
+ this.outgoingConnectionsRegistry = outgoingConnectionsRegistry;
+ this.logIdFactory = logIdFactory;
+ this.remoteMta = remoteMta;
+
+ }
+
+ /**
+ * Delivers the mail to the SMTP server running on the specified host.
+ *
+ * @param inetAddress
+ * The receiving SMTP host. The name (if there is one) must
+ * already be resolved (using dnsJava).
+ * @throws PostponeException
+ * if it has not even tried connecting to the host,
because it
+ * is likely that the host is busy at this moment.
+ */
+ public void transmit(Mail mail, InetAddress inetAddress)
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ this.mail = mail;
+ SmartClient smartClient = null;
+ try {
+ outgoingConnectionsRegistry.openConnection(inetAddress);
+ } catch (PostponeException e) {
+ e.setRemoteMta(remoteMta);
+ throw e;
+ }
+ try {
+ smartClient = clientFactory.create(inetAddress);
+ smartClient.from(mail.from.getSmtpText());
+ List<RecipientRejection> recipientRejections =
+ new ArrayList<RecipientRejection>();
+ List<Recipient> acceptedRecipients = new
ArrayList<Recipient>();
+ for (Recipient recipient : mail.recipients) {
+ try {
+ smartClient.to(recipient.sourceRouteStripped());
+ acceptedRecipients.add(recipient);
+ } catch (SMTPException e) {
+ RemoteMtaErrorResponseException sendException =
+ new RemoteMtaErrorResponseException(e,
remoteMta);
+ recipientRejections.add(new
RecipientRejection(recipient,
+ sendException));
+ String logId = logIdFactory.next();
+ sendException.initLogId(logId);
+ logger.debug("Recipient " + recipient
+ + " was rejected/failed. Log-ID=" + logId
+ + ". Continuing with the next recipient if
one "
+ + "exists. " + e.getResponse());
+ }
+ }
+ if (acceptedRecipients.isEmpty()) {
+ logger.debug("All recipients were rejected");
+ throw new
RecipientsWereRejectedException(recipientRejections);
+ }
+ smartClient.dataStart();
+ writeDataTo(smartClient);
+ smartClient.dataEnd();
+ if (!recipientRejections.isEmpty())
+ throw new
RecipientsWereRejectedException(recipientRejections);
+ else
+ return;
+ } catch (SMTPException e) {
+ throw new RemoteMtaErrorResponseException(e, remoteMta);
+ } catch (UnknownHostException e) {
+ // impossible
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new SendException("Connection failed: " + e.toString(),
e,
+ new EnhancedStatus(450, "4.4.0",
+ "No answer from host or bad connection"),
remoteMta);
+ } finally {
+ if (smartClient != null) {
+ smartClient.quit();
+ }
+ outgoingConnectionsRegistry.releaseConnection(inetAddress);
+ }
+ }
+
+ private void writeDataTo(SmartClient smartClient) throws IOException {
+ SmartClientOutputStreamAdapter out =
+ new SmartClientOutputStreamAdapter(smartClient);
+ mail.mailData.writeTo(out);
+ }
+}
=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/host/MailToHostTransmitterFactory.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,49 @@
+package mireka.transmission.immediate.host;
+
+import mireka.smtp.ClientFactory;
+import mireka.transmission.immediate.RemoteMta;
+import mireka.transmission.queuing.LogIdFactory;
+
+/**
+ * Factory for creating {@link MailToHostTransmitter} instances.
+ */
+public class MailToHostTransmitterFactory {
+ private ClientFactory clientFactory;
+ private OutgoingConnectionsRegistry outgoingConnectionRegistry;
+ private LogIdFactory logIdFactory;
+
+ public MailToHostTransmitter create(RemoteMta remoteMta) {
+ return new MailToHostTransmitter(clientFactory,
+ outgoingConnectionRegistry, logIdFactory, remoteMta);
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setClientFactory(ClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setOutgoingConnectionRegistry(
+ OutgoingConnectionsRegistry outgoingConnectionRegistry) {
+ this.outgoingConnectionRegistry = outgoingConnectionRegistry;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public OutgoingConnectionsRegistry getOutgoingConnectionRegistry() {
+ return outgoingConnectionRegistry;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public void setLogIdFactory(LogIdFactory logIdFactory) {
+ this.logIdFactory = logIdFactory;
+ }
+
+}
=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/host/OutgoingConnectionsRegistry.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,93 @@
+package mireka.transmission.immediate.host;
+
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.annotation.concurrent.GuardedBy;
+
+import mireka.smtp.EnhancedStatus;
+import mireka.transmission.immediate.PostponeException;
+
+/**
+ * OutgoingConnectionsRegistry can tell if too many connections are open
to a
+ * specific host at the moment.
+ */
+public class OutgoingConnectionsRegistry {
+ /**
+ * 0 means no limit
+ */
+ private int maxConnectionsToHost = 3;
+ private final Random random = new Random();
+
+ @GuardedBy("this")
+ private final Map<InetAddress, Integer> connections =
+ new HashMap<InetAddress, Integer>();
+
+ public synchronized void openConnection(InetAddress address)
+ throws PostponeException {
+ if (noLimit())
+ return;
+
+ Integer connectionCountObject = connections.get(address);
+ int connectionCount =
+ connectionCountObject == null ? 0 : connectionCountObject;
+ if (connectionCount >= maxConnectionsToHost) {
+ int recommendedDelay = 5 + random.nextInt(11);
+ throw new PostponeException(recommendedDelay, new
EnhancedStatus(
+ 451, "4.4.5",
+ "Too much connections to the destination system"),
+ "There are already " + connectionCount
+ + " connections to host " + address
+ + ", it must not be connected now. "
+ + recommendedDelay
+ + " s of delay is recommended before the next "
+ + "attempt.");
+ }
+
+ connectionCount++;
+ connections.put(address, connectionCount);
+ }
+
+ public synchronized void releaseConnection(InetAddress address) {
+ if (noLimit())
+ return;
+
+ Integer connectionCountObject = connections.get(address);
+ int connectionCount =
+ connectionCountObject == null ? 0 : connectionCountObject;
+ if (connectionCount <= 0)
+ throw new RuntimeException("Assertion failed, connections="
+ + connections.toString() + " address=" + address);
+
+ if (connectionCount == 1) {
+ connections.remove(address);
+ } else {
+ connectionCount--;
+ connections.put(address, connectionCount);
+ }
+ }
+
+ @GuardedBy("this")
+ private boolean noLimit() {
+ return maxConnectionsToHost == 0;
+ }
+
+ /**
+ * @category GETSET
+ */
+ public int getMaxConnectionsToHost() {
+ return maxConnectionsToHost;
+ }
+
+ /**
+ * Sets the maximum count of simultaneous connections to a single host.
+ *
+ * @param maxConnectionsToHost
+ * 0 means no limit
+ */
+ public void setMaxConnectionsToHost(int maxConnectionsToHost) {
+ this.maxConnectionsToHost = maxConnectionsToHost;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/src/mireka/transmission/immediate/host/SmartClientOutputStreamAdapter.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,44 @@
+package mireka.transmission.immediate.host;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.subethamail.smtp.client.SmartClient;
+
+/**
+ * An {@link OutputStream} which redirects all writes to a {@link
SmartClient}.
+ */
+class SmartClientOutputStreamAdapter extends OutputStream {
+
+ private final SmartClient client;
+ private byte[] buffer = new byte[1];
+
+ public SmartClientOutputStreamAdapter(SmartClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ buffer[0] = (byte) b;
+ client.dataWrite(buffer, 1);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ client.dataWrite(b, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (off == 0) {
+ client.dataWrite(b, len);
+ } else {
+ if (len > buffer.length) {
+ buffer = new byte[len];
+ }
+ System.arraycopy(b, off, buffer, 0, len);
+ client.dataWrite(buffer, len);
+ }
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/mireka/transmission/immediate/host/package-info.java Sun
May 5 07:37:27 2013
@@ -0,0 +1,5 @@
+/**
+ * Provides a class which makes a one time attempt to transmit a mail to a
+ * specific SMTP host.
+ */
+package mireka.transmission.immediate.host;
=======================================
--- /dev/null
+++ /trunk/test/mireka/transmission/immediate/ResponseFactoryTest.java Sun
May 5 07:37:27 2013
@@ -0,0 +1,46 @@
+package mireka.transmission.immediate;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import mireka.smtp.EnhancedStatus;
+import mireka.smtp.MailSystemStatus;
+
+import org.junit.Test;
+import org.subethamail.smtp.client.SMTPClient.Response;
+
+public class ResponseFactoryTest {
+
+ private static final String ENHANCED_EXAMPLE =
+ "5.7.1 Delivery not authorized, message refused";
+ private static final String ORIGINAL_EXAMPLE =
+ "Delivery not authorized, message refused";
+
+ @Test
+ public void testCreateResponseLookingForEnhancedStatusCode_original() {
+ Response srcResponse = new Response(500, ENHANCED_EXAMPLE);
+ MailSystemStatus basicResponse =
+ new ResponseParser()
+ .createResponseLookingForEnhancedStatusCode(srcResponse);
+ assertThat(basicResponse, instanceOf(EnhancedStatus.class));
+ EnhancedStatus enhancedResponse = (EnhancedStatus) basicResponse;
+ assertEquals("5.7.1", enhancedResponse.getEnhancedStatusCode());
+
+ assertEquals("Delivery not authorized, message refused",
+ enhancedResponse.getMessage());
+ assertEquals(500, enhancedResponse.getSmtpReplyCode());
+ }
+
+ @Test
+ public void testCreateResponseLookingForEnhancedStatusCode_enhanced() {
+ Response srcResponse = new Response(500, ORIGINAL_EXAMPLE);
+ MailSystemStatus basicResponse =
+ new ResponseParser()
+ .createResponseLookingForEnhancedStatusCode(srcResponse);
+ assertThat(basicResponse, instanceOf(Rfc821Status.class));
+
+ assertEquals("Delivery not authorized, message refused",
+ basicResponse.getMessage());
+ assertEquals(500, basicResponse.getSmtpReplyCode());
+ }
+}
=======================================
--- /dev/null
+++
/trunk/test/mireka/transmission/immediate/direct/ImmediateSenderTest.java
Sun May 5 07:37:27 2013
@@ -0,0 +1,383 @@
+package mireka.transmission.immediate.direct;
+
+import static mireka.ExampleAddress.ADA_ADDRESS_LITERAL_AS_RECIPIENT;
+import static mireka.ExampleAddress.GLOBAL_POSTMASTER_AS_RECIPIENT;
+import static mireka.ExampleAddress.HOST1_EXAMPLE_COM_NAME;
+import static mireka.ExampleAddress.HOST2_EXAMPLE_COM_NAME;
+import static mireka.ExampleAddress.IP;
+import static mireka.ExampleAddress.IP1;
+import static mireka.ExampleAddress.IP2;
+import static mireka.ExampleAddress.IP_ADDRESS_ONLY;
+import static mireka.ExampleAddress.JANE_AS_RECIPIENT;
+import static mireka.ExampleAddress.JOHN_AS_RECIPIENT;
+import static mireka.ExampleAddress.NANCY_NET_AS_RECIPIENT;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+import mireka.ExampleMail;
+import mireka.address.Domain;
+import mireka.address.Recipient;
+import mireka.smtp.EnhancedStatus;
+import mireka.transmission.Mail;
+import mireka.transmission.immediate.PostponeException;
+import mireka.transmission.immediate.RecipientsWereRejectedException;
+import mireka.transmission.immediate.RemoteMta;
+import mireka.transmission.immediate.SendException;
+import mireka.transmission.immediate.dns.AddressLookup;
+import mireka.transmission.immediate.dns.AddressLookupFactory;
+import mireka.transmission.immediate.dns.MxLookup;
+import mireka.transmission.immediate.dns.MxLookupFactory;
+import mireka.transmission.immediate.host.MailToHostTransmitter;
+import mireka.transmission.immediate.host.MailToHostTransmitterFactory;
+import mockit.Expectations;
+import mockit.Mocked;
+import mockit.NonStrictExpectations;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xbill.DNS.Name;
+
+public class ImmediateSenderTest {
+
+ @Mocked
+ private MxLookupFactory mxLookupFactory;
+
+ @Mocked
+ private MxLookup mxLookup;
+
+ @Mocked
+ private AddressLookupFactory addressLookupFactory;
+
+ @Mocked
+ private AddressLookup addressLookup;
+
+ @Mocked
+ private MailToHostTransmitterFactory mailToHostTransmitterFactory;
+
+ @Mocked
+ private MailToHostTransmitter mailToHostTransmitter;
+
+ private Mail mail = ExampleMail.simple();
+ private Mail adaAddressLiteralMail;
+ private Mail janeJoeMail;
+
+ private DirectImmediateSender sender;
+
+ private SendException permanentSendException = new SendException(
+ "Example permanent failure",
+ EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
+ private SendException transientSendException = new SendException(
+ "Example transient failure",
+ EnhancedStatus.TRANSIENT_LOCAL_ERROR_IN_PROCESSING);
+ private static final PostponeException POSTPONE_EXCEPTION =
+ new PostponeException(
+ 10,
+
EnhancedStatus.TRANSIENT_SYSTEM_NOT_ACCEPTING_NETWORK_MESSAGES,
+ "Test exception");
+
+ @Before
+ public void initialize() {
+ adaAddressLiteralMail = ExampleMail.simple();
+ adaAddressLiteralMail.recipients =
+ Arrays.asList(ADA_ADDRESS_LITERAL_AS_RECIPIENT);
+ janeJoeMail = ExampleMail.simple();
+ janeJoeMail.recipients =
+ Arrays.asList(JANE_AS_RECIPIENT, JOHN_AS_RECIPIENT);
+ sender =
+ new DirectImmediateSender(mxLookupFactory,
addressLookupFactory,
+ mailToHostTransmitterFactory);
+
+ new NonStrictExpectations() {
+ {
+ mailToHostTransmitterFactory.create((RemoteMta) any);
+ result = mailToHostTransmitter;
+
+ mxLookupFactory.create((Domain) any);
+ result = mxLookup;
+
+ addressLookupFactory.create((Name) any);
+ result = addressLookup;
+ }
+ };
+ }
+
+ @Test
+ public void testSendToAddressLiteralVerifyNoDns() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ times = 0;
+
+ addressLookup.queryAddresses();
+ times = 0;
+
+ mailToHostTransmitter.transmit((Mail) any, IP);
+ }
+ };
+
+ sender.send(adaAddressLiteralMail);
+ }
+
+ @Test
+ public void testSendToDomain() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = new InetAddress[] { IP_ADDRESS_ONLY };
+
+ mailToHostTransmitter.transmit((Mail) any,
IP_ADDRESS_ONLY);
+ }
+ };
+
+ sender.send(mail);
+
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendToDifferentDomain() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ mail.recipients =
+ Arrays.asList(JANE_AS_RECIPIENT, NANCY_NET_AS_RECIPIENT);
+ sender.send(mail);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendToGlobalPostmaster() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ mail.recipients =
+ Arrays.asList((Recipient) GLOBAL_POSTMASTER_AS_RECIPIENT);
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstMxCannotBeResolved() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = permanentSendException;
+ result = new InetAddress[] { IP2 };
+
+ mailToHostTransmitter.transmit((Mail) any, IP2);
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstHostHasTransientProblem() throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = transientSendException;
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = null;
+ }
+ };
+
+ sender.send(mail);
+
+ }
+
+ private void twoMxDnsExpectation() throws SendException {
+ new NonStrictExpectations() {
+ {
+ mxLookup.queryMxTargets();
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+ times = 1;
+
+ addressLookup.queryAddresses();
+ result = new InetAddress[] { IP1 };
+ result = new InetAddress[] { IP2 };
+ times = 2;
+ }
+ };
+ }
+
+ @Test(expected = SendException.class)
+ public void testSendFirstHostHasPermanentProblem() throws
SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result =
+ new Name[] { HOST1_EXAMPLE_COM_NAME,
+ HOST2_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = new InetAddress[] { IP1 };
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = permanentSendException;
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendFirstHostHasTransientSecondHasPermanentProblem()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = transientSendException;
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = permanentSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("Exception expected");
+ } catch (SendException e) {
+ assertFalse(e.errorStatus().shouldRetry());
+ }
+
+ }
+
+ @Test
+ public void testSendFirstHostPostponed() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = null;
+ }
+ };
+
+ sender.send(mail);
+
+ }
+
+ @Test
+ public void testSendFirstHostPostponedSecondHasTransientProblem()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = transientSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("Exception expected");
+ } catch (SendException e) {
+ assertTrue(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test(expected = PostponeException.class)
+ public void testSendBothPostponed() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ twoMxDnsExpectation();
+ new Expectations() {
+ {
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = POSTPONE_EXCEPTION;
+
+ mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
+ result = POSTPONE_EXCEPTION;
+ }
+ };
+
+ sender.send(mail);
+ }
+
+ @Test
+ public void testSendSingleHostPermanentlyCannotBeResolved()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = permanentSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("An exception must have been thrown");
+ } catch (SendException e) {
+ assertFalse(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test
+ public void testSendSingleHostTemporarilyCannotBeResolved()
+ throws SendException, RecipientsWereRejectedException,
+ PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = transientSendException;
+ }
+ };
+
+ try {
+ sender.send(mail);
+ fail("An exception must have been thrown");
+ } catch (SendException e) {
+ assertTrue(e.errorStatus().shouldRetry());
+ }
+ }
+
+ @Test(expected = PostponeException.class)
+ public void testSendSingleHostPostponeException() throws SendException,
+ RecipientsWereRejectedException, PostponeException {
+ new Expectations() {
+ {
+ mxLookup.queryMxTargets();
+ result = new Name[] { HOST1_EXAMPLE_COM_NAME };
+
+ addressLookup.queryAddresses();
+ result = new InetAddress[] { IP1 };
+
+ mailToHostTransmitter.transmit((Mail) any, IP1);
+ result = POSTPONE_EXCEPTION;
+ }
+ };
+
+ sender.send(mail);
+ }
+}
=======================================
--- /trunk/src/mireka/transmission/immediate/MailToHostTransmitter.java Wed
Jul 20 09:22:26 2011
+++ /dev/null
@@ -1,123 +0,0 @@
-package mireka.transmission.immediate;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.concurrent.NotThreadSafe;
-
-import mireka.address.Recipient;
-import mireka.smtp.ClientFactory;
-import mireka.smtp.EnhancedStatus;
-import mireka.transmission.Mail;
-import mireka.transmission.queuing.LogIdFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.subethamail.smtp.client.SMTPException;
-import org.subethamail.smtp.client.SmartClient;
-
-/**
- * MailToHostTransmitter transmits a mail to a specific host specified by
its IP
- * address.
- */
-@NotThreadSafe
-class MailToHostTransmitter {
- private final Logger logger = LoggerFactory
- .getLogger(MailToHostTransmitter.class);
- private final ClientFactory clientFactory;
- private OutgoingConnectionsRegistry outgoingConnectionsRegistry;
- private final LogIdFactory logIdFactory;
- private final RemoteMta remoteMta;
- private Mail mail;
-
- public MailToHostTransmitter(ClientFactory clientFactory,
- OutgoingConnectionsRegistry outgoingConnectionsRegistry,
- LogIdFactory logIdFactory, RemoteMta remoteMta) {
- this.clientFactory = clientFactory;
- this.outgoingConnectionsRegistry = outgoingConnectionsRegistry;
- this.logIdFactory = logIdFactory;
- this.remoteMta = remoteMta;
-
- }
-
- /**
- * Delivers the mail to the SMTP server running on the specified host.
- *
- * @param inetAddress
- * The receiving SMTP host. The name (if there is one) must
- * already be resolved (using dnsJava).
- * @throws PostponeException
- * if it has not even tried connecting to the host,
because it
- * is likely that the host is busy at this moment.
- */
- public void transmit(Mail mail, InetAddress inetAddress)
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- this.mail = mail;
- SmartClient smartClient = null;
- try {
- outgoingConnectionsRegistry.openConnection(inetAddress);
- } catch (PostponeException e) {
- e.setRemoteMta(remoteMta);
- throw e;
- }
- try {
- smartClient = clientFactory.create(inetAddress);
- smartClient.from(mail.from.getSmtpText());
- List<RecipientRejection> recipientRejections =
- new ArrayList<RecipientRejection>();
- List<Recipient> acceptedRecipients = new
ArrayList<Recipient>();
- for (Recipient recipient : mail.recipients) {
- try {
- smartClient.to(recipient.sourceRouteStripped());
- acceptedRecipients.add(recipient);
- } catch (SMTPException e) {
- RemoteMtaErrorResponseException sendException =
- new RemoteMtaErrorResponseException(e,
remoteMta);
- recipientRejections.add(new
RecipientRejection(recipient,
- sendException));
- String logId = logIdFactory.next();
- sendException.initLogId(logId);
- logger.debug("Recipient " + recipient
- + " was rejected/failed. Log-ID=" + logId
- + ". Continuing with the next recipient if
one "
- + "exists. " + e.getResponse());
- }
- }
- if (acceptedRecipients.isEmpty()) {
- logger.debug("All recipients were rejected");
- throw new
RecipientsWereRejectedException(recipientRejections);
- }
- smartClient.dataStart();
- writeDataTo(smartClient);
- smartClient.dataEnd();
- if (!recipientRejections.isEmpty())
- throw new
RecipientsWereRejectedException(recipientRejections);
- else
- return;
- } catch (SMTPException e) {
- throw new RemoteMtaErrorResponseException(e, remoteMta);
- } catch (UnknownHostException e) {
- // impossible
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new SendException("Connection failed: " + e.toString(),
e,
- new EnhancedStatus(450, "4.4.0",
- "No answer from host or bad connection"),
remoteMta);
- } finally {
- if (smartClient != null) {
- smartClient.quit();
- }
- outgoingConnectionsRegistry.releaseConnection(inetAddress);
- }
- }
-
- private void writeDataTo(SmartClient smartClient) throws IOException {
- SmartClientOutputStreamAdapter out =
- new SmartClientOutputStreamAdapter(smartClient);
- mail.mailData.writeTo(out);
- }
-}
=======================================
---
/trunk/src/mireka/transmission/immediate/MailToHostTransmitterFactory.java
Thu Mar 17 05:06:50 2011
+++ /dev/null
@@ -1,45 +0,0 @@
-package mireka.transmission.immediate;
-
-import mireka.smtp.ClientFactory;
-import mireka.transmission.queuing.LogIdFactory;
-
-public class MailToHostTransmitterFactory {
- private ClientFactory clientFactory;
- private OutgoingConnectionsRegistry outgoingConnectionRegistry;
- private LogIdFactory logIdFactory;
-
- public MailToHostTransmitter create(RemoteMta remoteMta) {
- return new MailToHostTransmitter(clientFactory,
- outgoingConnectionRegistry, logIdFactory, remoteMta);
- }
-
- /**
- * @category GETSET
- */
- public void setClientFactory(ClientFactory clientFactory) {
- this.clientFactory = clientFactory;
- }
-
- /**
- * @category GETSET
- */
- public void setOutgoingConnectionRegistry(
- OutgoingConnectionsRegistry outgoingConnectionRegistry) {
- this.outgoingConnectionRegistry = outgoingConnectionRegistry;
- }
-
- /**
- * @category GETSET
- */
- public OutgoingConnectionsRegistry getOutgoingConnectionRegistry() {
- return outgoingConnectionRegistry;
- }
-
- /**
- * @category GETSET
- */
- public void setLogIdFactory(LogIdFactory logIdFactory) {
- this.logIdFactory = logIdFactory;
- }
-
-}
=======================================
---
/trunk/src/mireka/transmission/immediate/OutgoingConnectionsRegistry.java
Mon May 14 09:03:22 2012
+++ /dev/null
@@ -1,92 +0,0 @@
-package mireka.transmission.immediate;
-
-import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Random;
-
-import javax.annotation.concurrent.GuardedBy;
-
-import mireka.smtp.EnhancedStatus;
-
-/**
- * OutgoingConnectionsRegistry can tell if too many connections are open
to a
- * specific host at the moment.
- */
-public class OutgoingConnectionsRegistry {
- /**
- * 0 means no limit
- */
- private int maxConnectionsToHost = 3;
- private final Random random = new Random();
-
- @GuardedBy("this")
- private final Map<InetAddress, Integer> connections =
- new HashMap<InetAddress, Integer>();
-
- public synchronized void openConnection(InetAddress address)
- throws PostponeException {
- if (noLimit())
- return;
-
- Integer connectionCountObject = connections.get(address);
- int connectionCount =
- connectionCountObject == null ? 0 : connectionCountObject;
- if (connectionCount >= maxConnectionsToHost) {
- int recommendedDelay = 5 + random.nextInt(11);
- throw new PostponeException(recommendedDelay, new
EnhancedStatus(
- 451, "4.4.5",
- "Too much connections to the destination system"),
- "There are already " + connectionCount
- + " connections to host " + address
- + ", it must not be connected now. "
- + recommendedDelay
- + " s of delay is recommended before the next "
- + "attempt.");
- }
-
- connectionCount++;
- connections.put(address, connectionCount);
- }
-
- public synchronized void releaseConnection(InetAddress address) {
- if (noLimit())
- return;
-
- Integer connectionCountObject = connections.get(address);
- int connectionCount =
- connectionCountObject == null ? 0 : connectionCountObject;
- if (connectionCount <= 0)
- throw new RuntimeException("Assertion failed, connections="
- + connections.toString() + " address=" + address);
-
- if (connectionCount == 1) {
- connections.remove(address);
- } else {
- connectionCount--;
- connections.put(address, connectionCount);
- }
- }
-
- @GuardedBy("this")
- private boolean noLimit() {
- return maxConnectionsToHost == 0;
- }
-
- /**
- * @category GETSET
- */
- public int getMaxConnectionsToHost() {
- return maxConnectionsToHost;
- }
-
- /**
- * Sets the maximum count of simultaneous connections to a single host.
- *
- * @param maxConnectionsToHost
- * 0 means no limit
- */
- public void setMaxConnectionsToHost(int maxConnectionsToHost) {
- this.maxConnectionsToHost = maxConnectionsToHost;
- }
-}
=======================================
---
/trunk/src/mireka/transmission/immediate/SmartClientOutputStreamAdapter.java
Thu Apr 29 05:49:30 2010
+++ /dev/null
@@ -1,41 +0,0 @@
-package mireka.transmission.immediate;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.subethamail.smtp.client.SmartClient;
-
-class SmartClientOutputStreamAdapter extends OutputStream {
-
- private final SmartClient client;
- private byte[] buffer = new byte[1];
-
- public SmartClientOutputStreamAdapter(SmartClient client) {
- this.client = client;
- }
-
- @Override
- public void write(int b) throws IOException {
- buffer[0] = (byte) b;
- client.dataWrite(buffer, 1);
- }
-
- @Override
- public void write(byte[] b) throws IOException {
- client.dataWrite(b, b.length);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (off == 0) {
- client.dataWrite(b, len);
- } else {
- if (len > buffer.length) {
- buffer = new byte[len];
- }
- System.arraycopy(b, off, buffer, 0, len);
- client.dataWrite(buffer, len);
- }
- }
-
-}
=======================================
--- /trunk/test/mireka/submission/ResponseFactoryTest.java Mon May 14
09:03:22 2012
+++ /dev/null
@@ -1,47 +0,0 @@
-package mireka.submission;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-import mireka.smtp.EnhancedStatus;
-import mireka.smtp.MailSystemStatus;
-import mireka.transmission.immediate.Rfc821Status;
-import mireka.transmission.immediate.ResponseParser;
-
-import org.junit.Test;
-import org.subethamail.smtp.client.SMTPClient.Response;
-
-public class ResponseFactoryTest {
-
- private static final String ENHANCED_EXAMPLE =
- "5.7.1 Delivery not authorized, message refused";
- private static final String ORIGINAL_EXAMPLE =
- "Delivery not authorized, message refused";
-
- @Test
- public void testCreateResponseLookingForEnhancedStatusCode_original() {
- Response srcResponse = new Response(500, ENHANCED_EXAMPLE);
- MailSystemStatus basicResponse =
- new ResponseParser()
- .createResponseLookingForEnhancedStatusCode(srcResponse);
- assertThat(basicResponse, instanceOf(EnhancedStatus.class));
- EnhancedStatus enhancedResponse = (EnhancedStatus) basicResponse;
- assertEquals("5.7.1", enhancedResponse.getEnhancedStatusCode());
-
- assertEquals("Delivery not authorized, message refused",
- enhancedResponse.getMessage());
- assertEquals(500, enhancedResponse.getSmtpReplyCode());
- }
-
- @Test
- public void testCreateResponseLookingForEnhancedStatusCode_enhanced() {
- Response srcResponse = new Response(500, ORIGINAL_EXAMPLE);
- MailSystemStatus basicResponse =
- new ResponseParser()
- .createResponseLookingForEnhancedStatusCode(srcResponse);
- assertThat(basicResponse, instanceOf(Rfc821Status.class));
-
- assertEquals("Delivery not authorized, message refused",
- basicResponse.getMessage());
- assertEquals(500, basicResponse.getSmtpReplyCode());
- }
-}
=======================================
--- /trunk/test/mireka/transmission/immediate/ImmediateSenderTest.java Mon
May 14 09:03:22 2012
+++ /dev/null
@@ -1,365 +0,0 @@
-package mireka.transmission.immediate;
-
-import static mireka.ExampleAddress.*;
-import static org.junit.Assert.*;
-
-import java.net.InetAddress;
-import java.util.Arrays;
-
-import mireka.ExampleMail;
-import mireka.address.Domain;
-import mireka.address.Recipient;
-import mireka.smtp.EnhancedStatus;
-import mireka.transmission.Mail;
-import mireka.transmission.immediate.dns.AddressLookup;
-import mireka.transmission.immediate.dns.AddressLookupFactory;
-import mireka.transmission.immediate.dns.MxLookup;
-import mireka.transmission.immediate.dns.MxLookupFactory;
-import mockit.Expectations;
-import mockit.Mocked;
-import mockit.NonStrictExpectations;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.xbill.DNS.Name;
-
-public class ImmediateSenderTest {
-
- @Mocked
- private MxLookupFactory mxLookupFactory;
-
- @Mocked
- private MxLookup mxLookup;
-
- @Mocked
- private AddressLookupFactory addressLookupFactory;
-
- @Mocked
- private AddressLookup addressLookup;
-
- @Mocked
- private MailToHostTransmitterFactory mailToHostTransmitterFactory;
-
- @Mocked
- private MailToHostTransmitter mailToHostTransmitter;
-
- private Mail mail = ExampleMail.simple();
- private Mail adaAddressLiteralMail;
- private Mail janeJoeMail;
-
- private ImmediateSender sender;
-
- private SendException permanentSendException = new SendException(
- "Example permanent failure",
- EnhancedStatus.PERMANENT_UNABLE_TO_ROUTE);
- private SendException transientSendException = new SendException(
- "Example transient failure",
- EnhancedStatus.TRANSIENT_LOCAL_ERROR_IN_PROCESSING);
- private static final PostponeException POSTPONE_EXCEPTION =
- new PostponeException(
- 10,
-
EnhancedStatus.TRANSIENT_SYSTEM_NOT_ACCEPTING_NETWORK_MESSAGES,
- "Test exception");
-
- @Before
- public void initialize() {
- adaAddressLiteralMail = ExampleMail.simple();
- adaAddressLiteralMail.recipients =
- Arrays.asList(ADA_ADDRESS_LITERAL_AS_RECIPIENT);
- janeJoeMail = ExampleMail.simple();
- janeJoeMail.recipients =
- Arrays.asList(JANE_AS_RECIPIENT, JOHN_AS_RECIPIENT);
- sender =
- new ImmediateSender(mxLookupFactory, addressLookupFactory,
- mailToHostTransmitterFactory);
-
- new NonStrictExpectations() {
- {
- mailToHostTransmitterFactory.create((RemoteMta) any);
- result = mailToHostTransmitter;
-
- mxLookupFactory.create((Domain) any);
- result = mxLookup;
-
- addressLookupFactory.create((Name) any);
- result = addressLookup;
- }
- };
- }
-
- @Test
- public void testSendToAddressLiteralVerifyNoDns() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- times = 0;
-
- addressLookup.queryAddresses();
- times = 0;
-
- mailToHostTransmitter.transmit((Mail) any, IP);
- }
- };
-
- sender.send(adaAddressLiteralMail);
- }
-
- @Test
- public void testSendToDomain() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP_ADDRESS_ONLY };
-
- mailToHostTransmitter.transmit((Mail) any,
IP_ADDRESS_ONLY);
- }
- };
-
- sender.send(mail);
-
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testSendToDifferentDomain() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- mail.recipients =
- Arrays.asList(JANE_AS_RECIPIENT, NANCY_NET_AS_RECIPIENT);
- sender.send(mail);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testSendToGlobalPostmaster() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- mail.recipients =
- Arrays.asList((Recipient) GLOBAL_POSTMASTER_AS_RECIPIENT);
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstMxCannotBeResolved() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = permanentSendException;
- result = new InetAddress[] { IP2 };
-
- mailToHostTransmitter.transmit((Mail) any, IP2);
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstHostHasTransientProblem() throws
SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
-
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = null;
- }
- };
-
- sender.send(mail);
-
- }
-
- private void twoMxDnsExpectation() throws SendException {
- new NonStrictExpectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
- times = 1;
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
- result = new InetAddress[] { IP2 };
- times = 2;
- }
- };
- }
-
- @Test(expected = SendException.class)
- public void testSendFirstHostHasPermanentProblem() throws
SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result =
- new Name[] { HOST1_EXAMPLE_COM_NAME,
- HOST2_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = permanentSendException;
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendFirstHostHasTransientSecondHasPermanentProblem()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = permanentSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("Exception expected");
- } catch (SendException e) {
- assertFalse(e.errorStatus().shouldRetry());
- }
-
- }
-
- @Test
- public void testSendFirstHostPostponed() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = null;
- }
- };
-
- sender.send(mail);
-
- }
-
- @Test
- public void testSendFirstHostPostponedSecondHasTransientProblem()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = transientSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("Exception expected");
- } catch (SendException e) {
- assertTrue(e.errorStatus().shouldRetry());
- }
- }
-
- @Test(expected = PostponeException.class)
- public void testSendBothPostponed() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- twoMxDnsExpectation();
- new Expectations() {
- {
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
-
- mailToHostTransmitter.transmit((Mail) any, (InetAddress)
any);
- result = POSTPONE_EXCEPTION;
- }
- };
-
- sender.send(mail);
- }
-
- @Test
- public void testSendSingleHostPermanentlyCannotBeResolved()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = permanentSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("An exception must have been thrown");
- } catch (SendException e) {
- assertFalse(e.errorStatus().shouldRetry());
- }
- }
-
- @Test
- public void testSendSingleHostTemporarilyCannotBeResolved()
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = transientSendException;
- }
- };
-
- try {
- sender.send(mail);
- fail("An exception must have been thrown");
- } catch (SendException e) {
- assertTrue(e.errorStatus().shouldRetry());
- }
- }
-
- @Test(expected = PostponeException.class)
- public void testSendSingleHostPostponeException() throws SendException,
- RecipientsWereRejectedException, PostponeException {
- new Expectations() {
- {
- mxLookup.queryMxTargets();
- result = new Name[] { HOST1_EXAMPLE_COM_NAME };
-
- addressLookup.queryAddresses();
- result = new InetAddress[] { IP1 };
-
- mailToHostTransmitter.transmit((Mail) any, IP1);
- result = POSTPONE_EXCEPTION;
- }
- };
-
- sender.send(mail);
- }
-}
=======================================
--- /trunk/setup/conf/submission/queues.js Sun Mar 24 12:07:38 2013
+++ /trunk/setup/conf/submission/queues.js Sun May 5 07:37:27 2013
@@ -55,7 +55,7 @@
logIdFactory: logIdFactory,
});

-immediateSenderFactory = setup(ImmediateSenderFactory, {
+immediateSenderFactory = setup(DirectImmediateSenderFactory, {
mailToHostTransmitterFactory: mailToHostTransmitterFactory
});

=======================================
--- /trunk/setup/lib/configuration.js Sun Mar 24 12:07:38 2013
+++ /trunk/setup/lib/configuration.js Sun May 5 07:37:27 2013
@@ -35,7 +35,8 @@
importPackage(Packages.mireka.transmission.dsn);
importPackage(Packages.mireka.transmission.queue);
importPackage(Packages.mireka.transmission.queuing);
-importPackage(Packages.mireka.transmission.immediate);
+importPackage(Packages.mireka.transmission.immediate.direct);
+importPackage(Packages.mireka.transmission.immediate.host);

/*
* Make the include function global. The include function reads and
executes
=======================================
--- /trunk/src/mireka/smtp/EnhancedStatus.java Mon May 14 09:03:22 2012
+++ /trunk/src/mireka/smtp/EnhancedStatus.java Sun May 5 07:37:27 2013
@@ -4,14 +4,12 @@
import java.io.IOException;
import java.io.StringReader;

-import mireka.transmission.immediate.ResponseParser;
import mireka.transmission.immediate.Rfc821Status;
import mireka.util.Multiline;

/**
* These class represents an SMTP status which includes enhanced status
code.
*
- * @see ResponseParser
* @see <a href="http://tools.ietf.org/html/rfc3463">RFC 3463 - Enhanced
Mail
* System Status Codes</a>
* @see <a href="http://tools.ietf.org/html/rfc2034">RFC 2034 - SMTP
Service
@@ -76,6 +74,7 @@
+ Integer.toString(smtpReplyCode));
}

+ @Override
public int getSmtpReplyCode() {
return smtpReplyCode;
}
@@ -84,6 +83,7 @@
return enhancedStatusCode;
}

+ @Override
public String getMessage() {
return message;
}
=======================================
--- /trunk/src/mireka/transmission/immediate/ImmediateSender.java Wed Jul
20 09:22:26 2011
+++ /trunk/src/mireka/transmission/immediate/ImmediateSender.java Sun May
5 07:37:27 2013
@@ -1,51 +1,14 @@
package mireka.transmission.immediate;

-import java.net.InetAddress;
-
-import javax.annotation.concurrent.NotThreadSafe;
-
-import mireka.address.AddressLiteral;
-import mireka.address.Domain;
-import mireka.address.DomainPart;
-import mireka.address.Recipient;
-import mireka.address.RemotePart;
-import mireka.address.RemotePartContainingRecipient;
import mireka.transmission.Mail;
-import mireka.transmission.immediate.dns.AddressLookupFactory;
-import mireka.transmission.immediate.dns.MxLookupFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xbill.DNS.Name;

/**
- * ImmediateSender sends a mail to a domain, which may include attempting
- * delivery to more than one MX hosts of the domain until a working one is
- * found. If it cannot transmit the mail to any of the MX hosts of the
domain,
- * then it throws an exception, it does not retry later.
- * <p>
- * TODO: if a recipient is rejected because of a transient failure, then it
- * should be retried on another host.
+ * An ImmediateSender makes a single, synchronous attempt to deliver mail
to
+ * a remote system.
*/
-@NotThreadSafe
-public class ImmediateSender {
- private final Logger logger = LoggerFactory
- .getLogger(ImmediateSender.class);
- private final MxLookupFactory mxLookupFactory;
- private final AddressLookupFactory addressLookupFactory;
- private final MailToHostTransmitterFactory
mailToHostTransmitterFactory;
- private Mail mail;
-
- ImmediateSender(MxLookupFactory mxLookupFactory,
- AddressLookupFactory addressLookupFactory,
- MailToHostTransmitterFactory mailToHostTransmitterFactory) {
- this.mxLookupFactory = mxLookupFactory;
- this.addressLookupFactory = addressLookupFactory;
- this.mailToHostTransmitterFactory = mailToHostTransmitterFactory;
- }
-
+public interface ImmediateSender {
/**
- * Transmits mail to a single domain.
+ * Synchronously transmits mail to a single domain.
*
* @throws IllegalArgumentException
* if the domains of the recipients are not the same, or
if the
@@ -57,133 +20,5 @@
*/
public void send(Mail mail) throws SendException,
RecipientsWereRejectedException, IllegalArgumentException,
- PostponeException {
- this.mail = mail;
- RemotePart remotePart = commonRecipientRemotePart();
- if (remotePart instanceof AddressLiteral) {
- AddressLiteral addressLiteral = (AddressLiteral) remotePart;
- sendToAddressLiteral(addressLiteral);
- } else if (remotePart instanceof DomainPart) {
- Domain domain = ((DomainPart) remotePart).domain;
- sendToDomain(domain);
- } else {
- throw new RuntimeException();
- }
- }
-
- private RemotePart commonRecipientRemotePart()
- throws IllegalArgumentException {
- RemotePart result = null;
- for (Recipient recipient : mail.recipients) {
- if (!(recipient instanceof RemotePartContainingRecipient))
- throw new IllegalArgumentException(
- "Cannot send mail to non-remote address: " +
recipient);
- RemotePart remotePart =
- ((RemotePartContainingRecipient)
recipient).getMailbox()
- .getRemotePart();
- if (result == null) {
- result = remotePart;
- } else {
- if (!result.equals(remotePart))
- throw new IllegalArgumentException(
- "Recipients are expected to belong to the same
domain. "
- + " Recipient list contains both " +
result
- + " and " + remotePart);
- }
- }
- if (result == null)
- throw new IllegalArgumentException("recipient list is empty");
- return result;
- }
-
- private void sendToAddressLiteral(AddressLiteral target)
- throws SendException, RecipientsWereRejectedException,
- PostponeException {
- RemoteMta remoteMta =
- new RemoteMta(target.toString(), target.inetAddress()
- .getHostAddress());
- mailToHostTransmitterFactory.create(remoteMta).transmit(mail,
- target.inetAddress());
- }
-
- /**
- * Queries MX hosts of the domain and tries to transmit to the hosts
until
- * it is successful or no more hosts remain.
- *
- * @throws PostponeException
- * if transmission to all of the hosts must be postponed,
- * because all of them are assumed to be busy at this
moment.
- */
- private void sendToDomain(Domain domain) throws SendException,
- RecipientsWereRejectedException, PostponeException {
- Name[] mxNames = mxLookupFactory.create(domain).queryMxTargets();
-
- // 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 MX 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
- // MX host of the domain), so the function will continue, but it
must be
- // saved, because maybe there is no more host.
- SendException lastUnrecoverableDnsException = null;
- for (Name name : mxNames) {
- InetAddress[] addresses;
- try {
- addresses =
addressLookupFactory.create(name).queryAddresses();
- } catch (SendException e) {
- if (e.errorStatus().shouldRetry())
- lastRetryableException = e;
- else
- lastUnrecoverableDnsException = e;
- logger.debug("Looking up address of MX host " + name
- + " failed, continuing with the next MX host "
- + "if one is available: ", e.getMessage());
- continue;
- }
-
- try {
- for (InetAddress hostAddress : addresses) {
- RemoteMta remoteMta =
- new RemoteMta(name.toString(),
- hostAddress.getHostAddress());
-
mailToHostTransmitterFactory.create(remoteMta).transmit(
- mail, hostAddress);
- return;
- }
- } catch (PostponeException e) {
- lastPostponeException = e;
- logger.debug("Sending to SMTP host " + name
- + " must be postponed, continuing with the next "
- + "MX host if one is available: " +
e.getMessage());
- } catch (SendException e) {
- if (e.errorStatus().shouldRetry()) {
- // lastSendException = e;
- lastRetryableException = e;
- logger.debug("Sending to SMTP host " + name
- + " failed, continuing with the next "
- + "MX host if one is available: ",
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;
- }
+ PostponeException;
}
=======================================
--- /trunk/src/mireka/transmission/immediate/ImmediateSenderFactory.java
Thu Apr 29 05:49:30 2010
+++ /trunk/src/mireka/transmission/immediate/ImmediateSenderFactory.java
Sun May 5 07:37:27 2013
@@ -1,21 +1,17 @@
package mireka.transmission.immediate;

-import mireka.transmission.immediate.dns.AddressLookupFactory;
-import mireka.transmission.immediate.dns.MxLookupFactory;
-
-public class ImmediateSenderFactory {
- private MailToHostTransmitterFactory mailToHostTransmitterFactory;
-
- public ImmediateSender create() {
- return new ImmediateSender(new MxLookupFactory(),
- new AddressLookupFactory(), mailToHostTransmitterFactory);
- }
+/**
+ * Factory for {@link ImmediateSender}.
+ */
+public interface ImmediateSenderFactory {
+ /**
+ * Returns a new instance of {@link ImmediateSender}.
+ */
+ ImmediateSender create();

/**
- * @category GETSET
+ * Returns true if the created {@link ImmediateSender} requires that
all
+ * recipients of the mail to be sent have the same remote-part.
*/
- public void setMailToHostTransmitterFactory(
- MailToHostTransmitterFactory mailToHostTransmitterFactory) {
- this.mailToHostTransmitterFactory = mailToHostTransmitterFactory;
- }
+ boolean singleDomainOnly();
}
=======================================
--- /trunk/src/mireka/transmission/immediate/RecipientRejection.java Thu
Sep 30 05:26:42 2010
+++ /trunk/src/mireka/transmission/immediate/RecipientRejection.java Sun
May 5 07:37:27 2013
@@ -2,6 +2,10 @@

import mireka.address.Recipient;

+/**
+ * RecipientRejection contains the rejected recipient and the rejection
+ * exception.
+ */
public class RecipientRejection {
public final Recipient recipient;
public final SendException sendException;
=======================================
---
/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
Sun Mar 6 07:40:22 2011
+++
/trunk/src/mireka/transmission/immediate/RemoteMtaErrorResponseException.java
Sun May 5 07:37:27 2013
@@ -5,6 +5,9 @@

import org.subethamail.smtp.client.SMTPException;

+/**
+ * Thrown to indicate that the remote MTA returned an error message.
+ */
public class RemoteMtaErrorResponseException extends SendException {
private static final long serialVersionUID = -2886452940130526142L;
private final MailSystemStatus remoteMtaStatus;
=======================================
--- /trunk/src/mireka/transmission/immediate/ResponseParser.java Mon May 14
09:03:22 2012
+++ /trunk/src/mireka/transmission/immediate/ResponseParser.java Sun May 5
07:37:27 2013
@@ -12,7 +12,11 @@
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.client.SMTPClient.Response;

-public class ResponseParser {
+/**
+ * ResponseParser parses an SMTP reply to separate basic status code,
+ * enhanced status code, textual message.
+ */
+class ResponseParser {
private static final Pattern pattern = Pattern.compile("\\A([245]\\."
+ "(0|([1-9]\\d{0,2}))\\." + "(0|([1-9]\\d{0,2})))\\ ");
private final Logger logger =
LoggerFactory.getLogger(ResponseParser.class);
=======================================
--- /trunk/src/mireka/transmission/immediate/package-info.java Thu Apr 29
05:49:30 2010
+++ /trunk/src/mireka/transmission/immediate/package-info.java Sun May 5
07:37:27 2013
@@ -1,6 +1,6 @@
/**
- * Provides a class which makes a one-time attempt to transmit a mail to a
- * domain.
+ * Provides the API for services which make a one-time attempt to
synchronously
+ * transmit a mail to a remote system.
*/
package mireka.transmission.immediate;

=======================================
--- /trunk/src/mireka/transmission/queue/MailProcessor.java Thu Apr 29
05:49:30 2010
+++ /trunk/src/mireka/transmission/queue/MailProcessor.java Sun May 5
07:37:27 2013
@@ -2,8 +2,35 @@

import mireka.transmission.LocalMailSystemException;

+/**
+ * A MailProcessor object is responsible to process the mail which has
come to the
+ * head of a queue. It is created by a {@link MailProcessorFactory}, which
+ * received the mail object for which this instance will be responsible.
+ */
public interface MailProcessor {

+ /**
+ * It is called to process the mail for which this instance is
responsible.
+ * If this function returns without throwing an exception, then the
queue
+ * deletes the mail.
+ * <p>
+ * Note: this function is allowed to throw an exception only if there
is a
+ * <b>local</b> error. If, for example, a remote mail system cannot be
+ * connected, then it must not throw an exception. Of course,
appropriate
+ * measures must be taken, for example by moving the mail into a retry
+ * queue, but the processing must be considered successful.
+ *
+ * @throws LocalMailSystemException
+ * if it cannot process its mail. The future of the mail
depends
+ * on the {@link LocalMailSystemException#errorStatus()}
of the
+ * thrown exception. If the exception indicates a transient
+ * error, then the mail remains in the queue, and a new
+ * MailProcessor object will be created and run 5 minutes
later.
+ * This will be repeated up to 24 hours. If the exception
+ * indicates a permanent error then the queue will attempt
to
+ * move the mail into an error folder, where the mail
system
+ * administrator can examine it later.
+ */
void run() throws LocalMailSystemException;

}
=======================================
--- /trunk/src/mireka/transmission/queuing/QueuingTransmitter.java Wed Jul
20 09:22:26 2011
+++ /trunk/src/mireka/transmission/queuing/QueuingTransmitter.java Sun May
5 07:37:27 2013
@@ -21,13 +21,14 @@
import org.slf4j.LoggerFactory;

public class QueuingTransmitter implements Transmitter,
MailProcessorFactory {
- private Logger logger =
LoggerFactory.getLogger(QueuingTransmitter.class);
+ private final Logger logger =
LoggerFactory.getLogger(QueuingTransmitter.class);
private ScheduleFileDirQueue queue;
private ImmediateSenderFactory immediateSenderFactory;
private RetryPolicy retryPolicy;
private LogIdFactory logIdFactory;
private TransmitterSummary summary;

+ @Override
public void transmit(Mail mail) throws QueueStorageException {
logger.debug("Mail received for transmission: {}", mail);
queueByRemotePart(mail);
=======================================
---
/trunk/test/mireka/transmission/immediate/MailToHostTransmitterTest.java
Sun May 20 09:18:59 2012
+++
/trunk/test/mireka/transmission/immediate/MailToHostTransmitterTest.java
Sun May 5 07:37:27 2013
@@ -12,6 +12,8 @@
import mireka.smtp.ClientFactory;
import mireka.smtp.EnhancedStatus;
import mireka.transmission.Mail;
+import mireka.transmission.immediate.host.MailToHostTransmitter;
+import mireka.transmission.immediate.host.OutgoingConnectionsRegistry;
import mireka.transmission.queuing.LogIdFactory;
import mockit.Expectations;
import mockit.Mocked;
=======================================
---
/trunk/test/mireka/transmission/immediate/OutgoingConnectionsRegistryTest.java
Thu Mar 17 05:06:50 2011
+++
/trunk/test/mireka/transmission/immediate/OutgoingConnectionsRegistryTest.java
Sun May 5 07:37:27 2013
@@ -5,6 +5,7 @@

import java.util.Map;

+import mireka.transmission.immediate.host.OutgoingConnectionsRegistry;
import mockit.Deencapsulation;

import org.junit.Test;
Reply all
Reply to author
Forward
0 new messages