Package mireka.transmission.queuing

Source Code of mireka.transmission.queuing.RetryPolicy$FailureHandler

package mireka.transmission.queuing;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import mireka.address.Recipient;
import mireka.transmission.LocalMailSystemException;
import mireka.transmission.Mail;
import mireka.transmission.Transmitter;
import mireka.transmission.dsn.DelayReport;
import mireka.transmission.dsn.DsnMailCreator;
import mireka.transmission.dsn.PermanentFailureReport;
import mireka.transmission.dsn.RecipientProblemReport;
import mireka.transmission.immediate.PostponeException;
import mireka.transmission.immediate.RecipientRejection;
import mireka.transmission.immediate.RecipientsWereRejectedException;
import mireka.transmission.immediate.RemoteMtaErrorResponseException;
import mireka.transmission.immediate.SendException;

import org.joda.time.DateTime;
import org.joda.time.Instant;
import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* RetryPolicy decides what actions are necessary after a transmission attempt
* failed and executes those actions.
*/
public class RetryPolicy {
    private final Logger logger = LoggerFactory.getLogger(RetryPolicy.class);
    private List<Period> retryPeriods = Arrays.asList(Period.minutes(3),
            Period.minutes(27), Period.minutes(30), Period.hours(2),
            Period.hours(2), Period.hours(2), Period.hours(2), Period.hours(2),
            Period.hours(2), Period.hours(2), Period.hours(2), Period.hours(2),
            Period.hours(2), Period.hours(3));
    /**
     * Elements indicates the count of failed delivery attempts after which a
     * delayed DSN mail must be sent. For example 3 means that a DSN must be
     * issued after the third failed attempt.
     */
    private List<Integer> delayReportPoints = new ArrayList<Integer>();
    private DsnMailCreator dsnMailCreator;
    private Transmitter dsnTransmitter;
    private Transmitter retryTransmitter;

    /**
     * Constructs a new empty instance, required attributes must be passed using
     * the setter methods later.
     */
    public RetryPolicy() {
        // nothing to do
    }

    /**
     * Constructs a new instance with all required dependencies.
     */
    public RetryPolicy(DsnMailCreator dsnMailCreator,
            Transmitter dsnTransmitter, Transmitter retryTransmitter) {
        this.dsnMailCreator = dsnMailCreator;
        this.dsnTransmitter = dsnTransmitter;
        this.retryTransmitter = retryTransmitter;
    }

    /**
     * @throws LocalMailSystemException
     *             if a bounce (DSN) mail cannot be created or passed to a queue
     */
    public void actOnEntireMailFailure(Mail mail, SendException exception)
            throws LocalMailSystemException {
        EntireMailFailureHandler failureHandler =
                new EntireMailFailureHandler(mail, exception);
        failureHandler.onFailure();
    }

    /**
     * @throws LocalMailSystemException
     *             if a bounce (DSN) mail cannot be created or passed to a queue
     */
    public void actOnRecipientsWereRejected(Mail mail,
            RecipientsWereRejectedException exception)
            throws LocalMailSystemException {
        RecipientsRejectedFailureHandler failureHandler =
                new RecipientsRejectedFailureHandler(mail, exception.rejections);
        failureHandler.onFailure();
    }

    public void actOnPostponeRequired(Mail mail, PostponeException e)
            throws LocalMailSystemException {
        mail.postpones++;
        if (mail.postpones <= 3) {
            Instant newScheduleDate =
                    new DateTime().plusSeconds(e.getRecommendedDelay())
                            .toInstant();
            mail.scheduleDate = newScheduleDate.toDate();
            retryTransmitter.transmit(mail);
            logger.debug("Delivery must be postponed to all hosts. "
                    + "Rescheduling the attempt. This is the " + mail.postpones
                    + ". postponing of this delivery attempt.");

        } else {
            logger.debug("Too much postponings of delivery attempt. "
                    + "The next would be the " + mail.postpones
                    + ". Attempt is considered to be a failure.");
            SendException sendException =
                    new SendException(
                            "Too much postponings of delivery attempt, attempt is considered to be a failure.",
                            e, e.getEnhancedStatus(), e.getRemoteMta());
            EntireMailFailureHandler failureHandler =
                    new EntireMailFailureHandler(mail, sendException);
            failureHandler.onFailure();
        }
    }

    private int maxAttempts() {
        return retryPeriods.size();
    }

    /**
     * @category GETSET
     */
    public void setRetryPeriods(List<Period> retryPeriods) {
        this.retryPeriods = retryPeriods;
    }

    /**
     * @category GETSET
     */
    public void setDelayReportPoints(List<Integer> delayReportPoints) {
        this.delayReportPoints = delayReportPoints;
    }

    public void addDelayReportPoint(int index) {
        this.delayReportPoints.add(index);
    }

    /**
     * @category GETSET
     */
    public void setDsnMailCreator(DsnMailCreator dsnMailCreator) {
        this.dsnMailCreator = dsnMailCreator;
    }

    /**
     * @category GETSET
     */
    public void setDsnTransmitter(Transmitter dsnTransmitter) {
        this.dsnTransmitter = dsnTransmitter;
    }

    /**
     * @category GETSET
     */
    public void setRetryTransmitter(Transmitter retryTransmitter) {
        this.retryTransmitter = retryTransmitter;
    }

    private class RecipientsRejectedFailureHandler extends FailureHandler {
        private final List<RecipientRejection> rejections;

        public RecipientsRejectedFailureHandler(Mail mail,
                List<RecipientRejection> rejections) {
            super(mail);
            this.rejections = rejections;
        }

        @Override
        protected List<SendingFailure> createFailures() {
            List<SendingFailure> result = new ArrayList<SendingFailure>();
            for (RecipientRejection rejection : rejections) {
                result.add(new SendingFailure(rejection.recipient,
                        rejection.sendException));
            }
            return result;
        }
    }

    private class EntireMailFailureHandler extends FailureHandler {

        private final SendException sendException;

        public EntireMailFailureHandler(Mail mail, SendException sendException) {
            super(mail);
            this.sendException = sendException;
        }

        @Override
        protected List<SendingFailure> createFailures() {
            List<SendingFailure> result = new ArrayList<SendingFailure>();
            for (Recipient recipient : mail.recipients) {
                result.add(new SendingFailure(recipient, sendException));
            }
            return result;
        }
    }

    private abstract class FailureHandler {
        private final Logger logger = LoggerFactory
                .getLogger(EntireMailFailureHandler.class);
        protected final Mail mail;

        private List<SendingFailure> failures;
        private List<SendingFailure> permanentFailures =
                new ArrayList<SendingFailure>();
        private List<SendingFailure> transientFailures =
                new ArrayList<SendingFailure>();
        private List<PermanentFailureReport> permanentFailureReports =
                new ArrayList<PermanentFailureReport>();
        private List<DelayReport> delayReports = new ArrayList<DelayReport>();

        public FailureHandler(Mail mail) {
            this.mail = mail;
        }

        public final void onFailure() throws LocalMailSystemException {
            mail.deliveryAttempts++;
            mail.postpones = 0;
            failures = createFailures();
            separatePermanentAndTemporaryFailures();
            createPermanentFailureReports();
            createDelayReports();
            sendDsnMail();
            rescheduleTemporaryFailures();
        }

        protected abstract List<SendingFailure> createFailures();

        private void separatePermanentAndTemporaryFailures() {
            for (SendingFailure failure : failures) {
                if (failure.exception.errorStatus().shouldRetry())
                    transientFailures.add(failure);
                else
                    permanentFailures.add(failure);
            }
            if (mail.deliveryAttempts > maxAttempts()
                    && !transientFailures.isEmpty()) {
                logger.debug("Giving up after the " + mail.deliveryAttempts
                        + ". transient failure. Considering it as "
                        + "a permanent failure.");
                permanentFailures.addAll(transientFailures);
                transientFailures.clear();
                return;
            }
        }

        private void createPermanentFailureReports() {
            for (SendingFailure failure : permanentFailures) {
                PermanentFailureReport report = new PermanentFailureReport();
                fillInRecipientFailureReport(report, failure);
                permanentFailureReports.add(report);
            }
        }

        private void fillInRecipientFailureReport(
                RecipientProblemReport report, SendingFailure failure) {
            SendException exception = failure.exception;
            report.recipient = failure.recipient;
            report.status = exception.errorStatus();
            report.remoteMta = exception.remoteMta();
            if (exception instanceof RemoteMtaErrorResponseException)
                report.remoteMtaDiagnosticStatus =
                        ((RemoteMtaErrorResponseException) exception)
                                .remoteMtaStatus();
            report.failureDate = exception.failureDate;
            report.logId = exception.getLogId();
        }

        private void createDelayReports() {
            if (!delayReportPoints.contains(mail.deliveryAttempts))
                return;
            for (SendingFailure failure : transientFailures) {
                DelayReport report = new DelayReport();
                fillInRecipientFailureReport(report, failure);
                delayReports.add(report);
            }
        }

        private void sendDsnMail() throws LocalMailSystemException {
            List<RecipientProblemReport> reports =
                    new ArrayList<RecipientProblemReport>();
            reports.addAll(permanentFailureReports);
            reports.addAll(delayReports);

            if (reports.isEmpty())
                return;
            if (mail.from.isNull()) {
                logger.debug("Failure or delay, but reverse-path is null, "
                        + "DSN must not be sent. "
                        + "Original mail itself was a notification.");
                return;
            }
            Mail dsnMail = dsnMailCreator.create(mail, reports);
            dsnTransmitter.transmit(dsnMail);
            logger.debug("DSN message is created with "
                    + permanentFailureReports.size()
                    + " permanent failures and " + delayReports.size()
                    + " delays and passed to the DSN transmitter.");
        }

        private void rescheduleTemporaryFailures()
                throws LocalMailSystemException {
            if (transientFailures.isEmpty())
                return;
            Period waitingPeriod = retryPeriods.get(mail.deliveryAttempts - 1);
            Instant newScheduleDate =
                    new DateTime().plus(waitingPeriod).toInstant();
            mail.scheduleDate = newScheduleDate.toDate();
            mail.recipients = calculateTemporarilyRejectedRecipientList();
            retryTransmitter.transmit(mail);
            logger.debug("Transient failure, the mail is scheduled for a "
                    + (mail.deliveryAttempts + 1) + ". attempt "
                    + waitingPeriod + " later on " + newScheduleDate);
        }

        private List<Recipient> calculateTemporarilyRejectedRecipientList() {
            List<Recipient> result = new ArrayList<Recipient>();
            for (SendingFailure failure : transientFailures) {
                result.add(failure.recipient);
            }
            return result;
        }
    }

    /**
     * SendingFailure stores failure information for a specific recipient.
     */
    private static class SendingFailure {
        public final Recipient recipient;
        public final SendException exception;

        public SendingFailure(Recipient recipient, SendException exception) {
            this.recipient = recipient;
            this.exception = exception;
        }
    }
}
TOP

Related Classes of mireka.transmission.queuing.RetryPolicy$FailureHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.