Skip to content

Commit

Permalink
JAMES-1877 Extract a utility for sending bounce
Browse files Browse the repository at this point in the history
  • Loading branch information
chibenwa committed Jan 10, 2017
1 parent bc52f33 commit 26c6d9c
Show file tree
Hide file tree
Showing 5 changed files with 641 additions and 109 deletions.
Expand Up @@ -56,6 +56,25 @@ public static SentMail.Builder sentMailBuilder() {
return new SentMail.Builder();
}

public static SentMail fromMail(Mail mail ) throws MessagingException {
return sentMailBuilder()
.sender(mail.getSender())
.recipients(mail.getRecipients())
.message(mail.getMessage())
.state(mail.getState())
.attributes(buildAttributesMap(mail))
.build();
}

private static ImmutableMap<String, Serializable> buildAttributesMap(Mail mail) {
Map<String, Serializable> result = new HashMap<String, Serializable>();
List<String> attributesNames = Lists.newArrayList(mail.getAttributeNames());
for (String attributeName: attributesNames) {
result.put(attributeName, mail.getAttribute(attributeName));
}
return ImmutableMap.copyOf(result);
}

public static FakeMailContext defaultContext() {
return builder().build();
}
Expand All @@ -80,7 +99,7 @@ public static class Builder {
private MailAddress sender;
private Optional<Collection<MailAddress>> recipients = Optional.absent();
private MimeMessage msg;
private Optional<Map<String, Serializable>> attributes = Optional.absent();
private Map<String, Serializable> attributes = new HashMap<String, Serializable>();
private Optional<String> state = Optional.absent();

public Builder sender(MailAddress sender) {
Expand All @@ -104,7 +123,12 @@ public Builder message(MimeMessage mimeMessage) {
}

public Builder attributes(Map<String, Serializable> attributes) {
this.attributes = Optional.of(attributes);
this.attributes.putAll(attributes);
return this;
}

public Builder attribute(String key, Serializable value) {
this.attributes.put(key, value);
return this;
}

Expand All @@ -115,7 +139,7 @@ public Builder state(String state) {

public SentMail build() {
return new SentMail(sender, recipients.or(ImmutableList.<MailAddress>of()), msg,
attributes.or(ImmutableMap.<String, Serializable>of()), state.or(Mail.DEFAULT));
ImmutableMap.copyOf(attributes), state.or(Mail.DEFAULT));
}
}

Expand Down Expand Up @@ -179,22 +203,73 @@ public String toString() {
}
}

public static class BouncedMail {
private final SentMail sentMail;
private final String message;
private final Optional<MailAddress> bouncer;

public BouncedMail(SentMail sentMail, String message, Optional<MailAddress> bouncer) {
this.sentMail = sentMail;
this.message = message;
this.bouncer = bouncer;
}

public SentMail getSentMail() {
return sentMail;
}

public String getMessage() {
return message;
}

public Optional<MailAddress> getBouncer() {
return bouncer;
}

@Override
public boolean equals(Object o) {
if (o instanceof BouncedMail) {
BouncedMail that = (BouncedMail) o;
return Objects.equal(this.sentMail, that.sentMail)
&& Objects.equal(this.message, that.message)
&& Objects.equal(this.bouncer, that.bouncer);
}
return false;
}

@Override
public int hashCode() {
return Objects.hashCode(sentMail, message, bouncer);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sentMail", sentMail)
.add("message", message)
.add("bouncer", bouncer)
.toString();
}
}

private final HashMap<String, Object> attributes;
private final List<SentMail> sentMails;
private final List<BouncedMail> bouncedMails;
private final Optional<Logger> logger;

private FakeMailContext(Optional<Logger> logger) {
attributes = new HashMap<String, Object>();
sentMails = new ArrayList<SentMail>();
bouncedMails = new ArrayList<BouncedMail>();
this.logger = logger;
}

public void bounce(Mail mail, String message) throws MessagingException {
// trivial implementation
bouncedMails.add(new BouncedMail(fromMail(mail), message, Optional.<MailAddress>absent()));
}

public void bounce(Mail mail, String message, MailAddress bouncer) throws MessagingException {
// trivial implementation
bouncedMails.add(new BouncedMail(fromMail(mail), message, Optional.fromNullable(bouncer)));
}

/**
Expand Down Expand Up @@ -263,21 +338,21 @@ public void removeAttribute(String name) {
}

public void sendMail(MimeMessage mimemessage) throws MessagingException {
sentMails.add(new SentMail.Builder()
sentMails.add(sentMailBuilder()
.message(mimemessage)
.build());
}

public void sendMail(MailAddress sender, Collection<MailAddress> recipients, MimeMessage msg) throws MessagingException {
sentMails.add(new SentMail.Builder()
sentMails.add(sentMailBuilder()
.recipients(recipients)
.sender(sender)
.message(msg)
.build());
}

public void sendMail(MailAddress sender, Collection<MailAddress> recipients, MimeMessage msg, String state) throws MessagingException {
sentMails.add(new SentMail.Builder()
sentMails.add(sentMailBuilder()
.recipients(recipients)
.message(msg)
.state(state)
Expand All @@ -286,22 +361,7 @@ public void sendMail(MailAddress sender, Collection<MailAddress> recipients, Mim
}

public void sendMail(Mail mail) throws MessagingException {
sentMails.add(new SentMail.Builder()
.sender(mail.getSender())
.recipients(mail.getRecipients())
.message(mail.getMessage())
.state(mail.getState())
.attributes(buildAttributesMap(mail))
.build());
}

private ImmutableMap<String, Serializable> buildAttributesMap(Mail mail) {
Map<String, Serializable> result = new HashMap<String, Serializable>();
List<String> attributesNames = Lists.newArrayList(mail.getAttributeNames());
for (String attributeName: attributesNames) {
result.put(attributeName, mail.getAttribute(attributeName));
}
return ImmutableMap.copyOf(result);
sentMails.add(fromMail(mail));
}

public void setAttribute(String name, Serializable object) {
Expand Down Expand Up @@ -372,6 +432,10 @@ public List<SentMail> getSentMails() {
return sentMails;
}

public List<BouncedMail> getBouncedMails() {
return bouncedMails;
}

@Override
public Logger getLogger() {
return logger.orNull();
Expand Down
@@ -0,0 +1,124 @@
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you 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 org.apache.james.transport.mailets.remoteDelivery;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;

import javax.mail.MessagingException;
import javax.mail.SendFailedException;

import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.apache.mailet.MailetContext;
import org.slf4j.Logger;

public class Bouncer {

public static final String DELIVERY_ERROR = "delivery-error";
private final RemoteDeliveryConfiguration configuration;
private final MessageComposer messageComposer;
private final MailetContext mailetContext;
private final Logger logger;

public Bouncer(RemoteDeliveryConfiguration configuration, MessageComposer messageComposer, MailetContext mailetContext, Logger logger) {
this.configuration = configuration;
this.messageComposer = messageComposer;
this.mailetContext = mailetContext;
this.logger = logger;
}

public void bounce(Mail mail, Exception ex) {
if (mail.getSender() == null) {
logger.debug("Null Sender: no bounce will be generated for " + mail.getName());
} else {
if (configuration.getBounceProcessor() != null) {
mail.setAttribute(DELIVERY_ERROR, messageComposer.getErrorMsg(ex));
mail.setState(configuration.getBounceProcessor());
try {
mailetContext.sendMail(mail);
} catch (MessagingException e) {
logger.debug("Exception re-inserting failed mail: ", e);
}
} else {
bounceWithMailetContext(mail, ex);
}
}
}


private void bounceWithMailetContext(Mail mail, Exception ex) {
logger.debug("Sending failure message " + mail.getName());
try {
mailetContext.bounce(mail, explanationText(mail, ex));
} catch (MessagingException me) {
logger.debug("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
} catch (Exception e) {
logger.debug("Encountered unexpected exception while bouncing message: " + e.getMessage());
}
}

public String explanationText(Mail mail, Exception ex) {
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
String machine;
try {
machine = configuration.getHeloNameProvider().getHeloName();

} catch (Exception e) {
machine = "[address unknown]";
}
String bounceBuffer = "Hi. This is the James mail server at " + machine + ".";
out.println(bounceBuffer);
out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
out.println("I include the list of recipients and the reason why I was unable to deliver");
out.println("your message.");
out.println();
for (MailAddress mailAddress : mail.getRecipients()) {
out.println(mailAddress);
}
if (ex instanceof MessagingException) {
if (((MessagingException) ex).getNextException() == null) {
out.println(ex.getMessage().trim());
} else {
Exception ex1 = ((MessagingException) ex).getNextException();
if (ex1 instanceof SendFailedException) {
out.println("Remote mail server told me: " + ex1.getMessage().trim());
} else if (ex1 instanceof UnknownHostException) {
out.println("Unknown host: " + ex1.getMessage().trim());
out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
} else if (ex1 instanceof ConnectException) {
// Already formatted as "Connection timed out: connect"
out.println(ex1.getMessage().trim());
} else if (ex1 instanceof SocketException) {
out.println("Socket exception: " + ex1.getMessage().trim());
} else {
out.println(ex1.getMessage().trim());
}
}
}
out.println();
return sout.toString();
}
}
Expand Up @@ -116,7 +116,7 @@ private static ExecutionResult onFailure(boolean permanent, Exception exeption)
private final DNSService dnsServer;
private final Metric outgoingMailsMetric;
private final Logger logger;
private final MailetContext mailetContext;
private final Bouncer bouncer;
private final VolatileIsDestroyed volatileIsDestroyed;
private final MessageComposer messageComposer;
private final Converter7Bit converter7Bit;
Expand All @@ -127,10 +127,10 @@ public DeliveryRunnable(MailQueue queue, RemoteDeliveryConfiguration configurati
this.dnsServer = dnsServer;
this.outgoingMailsMetric = outgoingMailsMetric;
this.logger = logger;
this.mailetContext = mailetContext;
this.volatileIsDestroyed = volatileIsDestroyed;
this.messageComposer = new MessageComposer(configuration);
this.converter7Bit = new Converter7Bit(mailetContext);
this.bouncer = new Bouncer(configuration, messageComposer, mailetContext, logger);
}

/**
Expand Down Expand Up @@ -189,7 +189,7 @@ private void attemptDelivery(Session session, Mail mail) throws MailQueue.MailQu
handleTemporaryFailure(mail, executionResult);
break;
case PERMANENT_FAILURE:
bounce(mail, executionResult.getException().orNull());
bouncer.bounce(mail, executionResult.getException().orNull());
break;
}
}
Expand All @@ -206,7 +206,7 @@ private void handleTemporaryFailure(Mail mail, ExecutionResult executionResult)
reAttemptDelivery(mail, retries);
} else {
logger.debug("Bouncing message " + mail.getName() + " after " + retries + " retries");
bounce(mail, new Exception("Too many retries failure. Bouncing after " + retries + " retries.", executionResult.getException().orNull()));
bouncer.bounce(mail, new Exception("Too many retries failure. Bouncing after " + retries + " retries.", executionResult.getException().orNull()));
}
}

Expand Down Expand Up @@ -711,39 +711,6 @@ private void logSendFailedException(SendFailedException sfe) {
}
}

private void bounce(Mail mail, Exception ex) {
if (mail.getSender() == null) {
logger.debug("Null Sender: no bounce will be generated for " + mail.getName());
}

if (configuration.getBounceProcessor() != null) {
// do the new DSN bounce setting attributes for DSN mailet
mail.setAttribute("delivery-error", messageComposer.getErrorMsg(ex));
mail.setState(configuration.getBounceProcessor());
// re-insert the mail into the spool for getting it passed to the dsn-processor
try {
mailetContext.sendMail(mail);
} catch (MessagingException e) {
// we shouldn't get an exception, because the mail was already processed
logger.debug("Exception re-inserting failed mail: ", e);
}
} else {
bounceWithMailetContext(mail, ex);
}
}


private void bounceWithMailetContext(Mail mail, Exception ex) {
logger.debug("Sending failure message " + mail.getName());
try {
mailetContext.bounce(mail, messageComposer.composeForBounce(mail, ex));
} catch (MessagingException me) {
logger.debug("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
} catch (Exception e) {
logger.debug("Encountered unexpected exception while bouncing message: " + e.getMessage());
}
}

/**
* Returns an Iterator over org.apache.mailet.HostAddress, a specialized
* subclass of javax.mail.URLName, which provides location information for
Expand Down

0 comments on commit 26c6d9c

Please sign in to comment.