org.xwiki.watchlist.internal.notification.WatchListEventMimeMessageIterator.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.watchlist.internal.notification.WatchListEventMimeMessageIterator.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.watchlist.internal.notification;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.codec.digest.DigestUtils;
import org.xwiki.mail.MimeMessageFactory;
import org.xwiki.mail.SessionFactory;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.watchlist.internal.UserAvatarAttachmentExtractor;
import org.xwiki.watchlist.internal.api.WatchListEvent;

import com.xpn.xwiki.api.Attachment;

/**
 * Generate {@link MimeMessage}s from {@link WatchListMessageData}s extracted by an iterator over a list of subscribers.
 *
 * @version $Id: 88551a662ab5d8ad0189e6f37d39b1ebeb379857 $
 * @since 7.1M1
 */
public class WatchListEventMimeMessageIterator implements Iterator<MimeMessage>, Iterable<MimeMessage> {
    /**
     * XWiki User Class first name property name.
     */
    public static final String XWIKI_USER_CLASS_FIRST_NAME_PROP = "first_name";

    /**
     * XWiki User Class last name property name.
     */
    public static final String XWIKI_USER_CLASS_LAST_NAME_PROP = "last_name";

    /**
     * Subscriber reference.
     */
    public static final String SUBSCRIBER_REFERENCE = "subscriberReference";

    /**
     * Template factory "attachments" parameter.
     */
    public static final String TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER = "attachments";

    private MimeMessageFactory<MimeMessage> factory;

    private Iterator<WatchListMessageData> subscriberIterator;

    private Map<String, Object> parameters;

    private Map<String, Object> factoryParameters;

    private UserAvatarAttachmentExtractor avatarExtractor;

    private List<Attachment> originalTemplateExtraParameters;

    private EntityReferenceSerializer<String> serializer;

    private SessionFactory sessionFactory;

    /**
     * @param subscriberIterator the iterator used to go through each subscriber and extract the
     *            {@link WatchListMessageData}
     * @param factory the factory to use to create a single MimeMessage
     * @param parameters the parameters from which to extract the factory source and factory parameters
     * @param avatarExtractor the {@link UserAvatarAttachmentExtractor} used once per message to extract an author's
     *            avatar attachment
     * @param serializer the {@link EntityReferenceSerializer} used to determine the mail's Message-ID
     * @param sessionFactory the session factory to be looked at when computing conversation IDs
     */
    public WatchListEventMimeMessageIterator(Iterator<WatchListMessageData> subscriberIterator,
            MimeMessageFactory<MimeMessage> factory, Map<String, Object> parameters,
            UserAvatarAttachmentExtractor avatarExtractor, EntityReferenceSerializer<String> serializer,
            SessionFactory sessionFactory) {
        this.subscriberIterator = subscriberIterator;
        this.factory = factory;
        this.parameters = parameters;
        this.avatarExtractor = avatarExtractor;
        this.serializer = serializer;
        this.sessionFactory = sessionFactory;

        this.factoryParameters = (Map<String, Object>) this.parameters
                .get(WatchListEventMimeMessageFactory.PARAMETERS_PARAMETER);

        // Save the list of attachments initially provided by the caller, since we will be constantly updating the
        // template factory's parameters for each message and we want to remember these to apply them on each message.
        this.originalTemplateExtraParameters = (List<Attachment>) this.factoryParameters
                .get(TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER);
    }

    @Override
    public Iterator<MimeMessage> iterator() {
        return this;
    }

    @Override
    public boolean hasNext() {
        return this.subscriberIterator.hasNext();
    }

    @Override
    public MimeMessage next() {
        MimeMessage message;
        WatchListMessageData watchListMessageData = this.subscriberIterator.next();

        try {
            // Update the values for this new message.
            updateFactoryParameters(factoryParameters, watchListMessageData);

            DocumentReference factorySource = watchListMessageData.getTemplateReference();

            // Use the factory to create the message.
            message = this.factory.createMessage(factorySource, factoryParameters);
            message.addRecipient(Message.RecipientType.TO, watchListMessageData.getAddress());

            // Set conversation headers.
            message = setConversationHeaders(message, watchListMessageData);
        } catch (MessagingException e) {
            throw new RuntimeException("Failed to create Mime Message, aborting mail sending for this batch", e);
        }

        return message;
    }

    /**
     * Update the factory's parameters with the values specific to the message we are going to send right now.
     *
     * @param watchListMessageData the messageData to use for the new message
     * @param factoryParameters the factory parameters we wish to update
     */
    private void updateFactoryParameters(Map<String, Object> factoryParameters,
            WatchListMessageData watchListMessageData) {
        Map<String, Object> velocityVariables = (Map<String, Object>) factoryParameters.get("velocityVariables");

        // Set the list of events, containing 1 event per document.
        List<WatchListEvent> events = watchListMessageData.getEvents();
        velocityVariables.put("events", events);

        // Compute the list of modified documents.
        List<String> modifiedDocuments = new ArrayList<>();
        for (WatchListEvent event : events) {
            if (!modifiedDocuments.contains(event.getPrefixedFullName())) {
                modifiedDocuments.add(event.getPrefixedFullName());
            }
        }
        velocityVariables.put("modifiedDocuments", modifiedDocuments);

        velocityVariables.put(XWIKI_USER_CLASS_FIRST_NAME_PROP, watchListMessageData.getFirstName());
        velocityVariables.put(XWIKI_USER_CLASS_LAST_NAME_PROP, watchListMessageData.getLastName());
        velocityVariables.put(SUBSCRIBER_REFERENCE, watchListMessageData.getUserReference());

        // Attach the avatars of the authors of the events we are notifying about.
        if (parameters.get(WatchListEventMimeMessageFactory.ATTACH_AUTHOR_AVATARS_PARAMETER) == Boolean.TRUE) {
            List<Attachment> templateExtraAttachments = getTemplateExtraAttachments(factoryParameters, events);
            factoryParameters.put(TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER, templateExtraAttachments);
        }

    }

    private List<Attachment> getTemplateExtraAttachments(Map<String, Object> factoryParameters,
            List<WatchListEvent> events) {
        // Append to any existing list of extra attachments specified by the caller.
        List<Attachment> templateExtraAttachments = new ArrayList<>();
        if (originalTemplateExtraParameters != null) {
            templateExtraAttachments.addAll(originalTemplateExtraParameters);
        }

        Set<DocumentReference> processedAuthors = new HashSet<DocumentReference>();
        for (WatchListEvent event : events) {
            for (DocumentReference authorReference : event.getAuthorReferences()) {
                // TODO: We do a minimal performance improvement here by not extracting a user's avatar twice for the
                // same message, but it should probably be the UserAvatarExtractor's job to support some caching
                // instead since that would also work across messages and would be much more useful.
                if (!processedAuthors.contains(authorReference)) {
                    Attachment avatarAttachment = avatarExtractor.getUserAvatar(authorReference);
                    if (avatarAttachment != null) {
                        templateExtraAttachments.add(avatarAttachment);
                    }
                    processedAuthors.add(authorReference);
                }
            }
        }
        return templateExtraAttachments;
    }

    private MimeMessage setConversationHeaders(MimeMessage originalMessage,
            WatchListMessageData watchListMessageData) throws MessagingException {
        // We need to copy the message in order to be able to set the Message-ID header without JavaMail overriding
        // it with a random one by default.
        MimeMessage result = new MimeMessage(originalMessage);

        DocumentReference documentReference = watchListMessageData.getEvents().get(0).getDocumentReference();
        String serializedReference = serializer.serialize(documentReference);

        // Using MD5 instead of the document reference string to avoid escaping issues. Also, we can not use hashcode()
        // on document reference because it is not consistent across JVM restarts. hashcode() over the reference string
        // would also be subject to many collisions, so it's not a good option either.
        String conversationIDPart = DigestUtils.md5Hex(serializedReference);
        String suffix = getConversationSuffix();
        String conversationID = String.format("<%s.XWiki.%s>", conversationIDPart, suffix);

        // Set the headers.
        result.setHeader("References", conversationID);
        result.setHeader("In-Reply-To", conversationID);

        return result;
    }

    /**
     * Compute the suffix of a conversation ID. It is done similar to what JavaMail does by default, using the session
     * to extract data (user, host, etc.) that can be set by the client, in case multiple instances of XWiki run on the
     * same machine.
     */
    private String getConversationSuffix() {
        String suffix = null;

        Session session = this.sessionFactory.create(Collections.<String, String>emptyMap());
        InternetAddress addr = InternetAddress.getLocalAddress(session);
        if (addr != null) {
            suffix = addr.getAddress();
        } else {
            // Worst-case default
            suffix = "xwiki@localhost";
        }

        return suffix;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("remove");
    }
}