package mireka.destination; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import mireka.MailData; import mireka.address.ReversePath; import mireka.filter.AbstractDataRecipientFilter; import mireka.filter.DataRecipientFilterAdapter; import mireka.filter.Filter; import mireka.filter.FilterType; import mireka.filter.MailTransaction; import mireka.filter.RecipientContext; import mireka.smtp.RejectExceptionExt; import mireka.transmission.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.subethamail.smtp.TooMuchDataException; /** * DestinationProcessorFilter groups recipients by their destinations and calls * the {@link MailDestination} or {@link SessionDestination} objects with the * recipients to which they are assigned. */ public class DestinationProcessorFilter implements FilterType { @Override public Filter createInstance(MailTransaction mailTransaction) { FilterImpl filterInstance = new FilterImpl(mailTransaction); return new DataRecipientFilterAdapter(filterInstance, mailTransaction); } private class FilterImpl extends AbstractDataRecipientFilter { private final Logger logger = LoggerFactory.getLogger(FilterImpl.class); private final Map<ResponsibleDestination, DestinationState> destinations = new LinkedHashMap<ResponsibleDestination, DestinationState>(); private Mail mail = new Mail(); protected FilterImpl(MailTransaction mailTransaction) { super(mailTransaction); } @Override public void from(ReversePath from) { mail.from = from; mail.receivedFromMtaAddress = mailTransaction.getRemoteInetAddress(); mail.receivedFromMtaName = mailTransaction.getMessageContext().getHelo(); } @Override public void recipient(RecipientContext recipientContext) throws RejectExceptionExt { if (recipientContext.isResponsibilityTransferred) return; if (!(recipientContext.getDestination() instanceof ResponsibleDestination)) return; ResponsibleDestination destination = (ResponsibleDestination) recipientContext.getDestination(); recipientContext.isResponsibilityTransferred = true; DestinationState destinationState = getOrCreateDestinationState(destination); destinationState.recipient(recipientContext); } private DestinationState getOrCreateDestinationState( ResponsibleDestination destination) { DestinationState destinationState = destinations.get(destination); if (destinationState == null) { if (destination instanceof MailDestination) { destinationState = new MailDestinationState( (MailDestination) destination); } else if (destination instanceof SessionDestination) { destinationState = new SessionDestinationState( (SessionDestination) destination); } else { throw new RuntimeException("Assertion failed"); } destinations.put(destination, destinationState); } return destinationState; } @Override public void data(MailData data) throws TooMuchDataException, RejectExceptionExt, IOException { mail.mailData = data; mail.arrivalDate = new Date(); mail.scheduleDate = mail.arrivalDate; for (Map.Entry<ResponsibleDestination, DestinationState> entry : destinations .entrySet()) { ResponsibleDestination destination = entry.getKey(); DestinationState destinationState = entry.getValue(); Mail destinationMail = mail.copy(); List<RecipientContext> recipientContexts = destinationState.recipientContexts; for (RecipientContext recipientContext : recipientContexts) { destinationMail.recipients.add(recipientContext.recipient); } if (recipientContexts.isEmpty()) { logger.debug("Destination {} has not accepted any of the " + "recipients to which it was assigned.", destination); } else { destinationState.data(destinationMail); } } } @Override public void done() { for (DestinationState destinationState : destinations.values()) { destinationState.done(); } } private abstract class DestinationState { final List<RecipientContext> recipientContexts = new ArrayList<RecipientContext>(); abstract void recipient(RecipientContext recipientContext) throws RejectExceptionExt; abstract void data(Mail mail) throws RejectExceptionExt, IOException; /** * Safely closes the session. */ abstract void done(); } private class MailDestinationState extends DestinationState { MailDestination destination; public MailDestinationState(MailDestination destination) { this.destination = destination; } @Override void recipient(RecipientContext recipientContext) { recipientContexts.add(recipientContext); } @Override void data(Mail mail) throws RejectExceptionExt { logger.debug("Sending {} recipients to {}", recipientContexts.size(), destination); destination.data(mail); } @Override void done() { // do nothing } } private class SessionDestinationState extends DestinationState { final SessionDestination destination; final Session session; boolean fromCalled = false; public SessionDestinationState(SessionDestination destination) { this.destination = destination; this.session = destination.createSession(); } @Override void recipient(RecipientContext recipientContext) throws RejectExceptionExt { if (!fromCalled) { fromCalled = true; session.from(mail.from); } session.recipient(recipientContext); recipientContexts.add(recipientContext); } @Override void data(Mail mail) throws RejectExceptionExt, IOException { logger.debug("Sending data for {} recipients to {}", recipientContexts.size(), destination); session.data(mail); } @Override void done() { try { session.done(); } catch (RuntimeException e) { logger.error( "Cleanup of session failed for " + destination, e); } } } } }