1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.watchlist.internal.notification

File WatchListEventMimeMessageIterator.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

12
63
9
1
271
150
16
0.25
7
9
1.78

Classes

Class Line # Actions
WatchListEventMimeMessageIterator 52 63 0% 16 12
0.8571428785.7%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.watchlist.internal.notification;
21   
22    import java.util.ArrayList;
23    import java.util.Collections;
24    import java.util.HashSet;
25    import java.util.Iterator;
26    import java.util.List;
27    import java.util.Map;
28    import java.util.Set;
29   
30    import javax.mail.Message;
31    import javax.mail.MessagingException;
32    import javax.mail.Session;
33    import javax.mail.internet.InternetAddress;
34    import javax.mail.internet.MimeMessage;
35   
36    import org.apache.commons.codec.digest.DigestUtils;
37    import org.xwiki.mail.MimeMessageFactory;
38    import org.xwiki.mail.SessionFactory;
39    import org.xwiki.model.reference.DocumentReference;
40    import org.xwiki.model.reference.EntityReferenceSerializer;
41    import org.xwiki.watchlist.internal.UserAvatarAttachmentExtractor;
42    import org.xwiki.watchlist.internal.api.WatchListEvent;
43   
44    import com.xpn.xwiki.api.Attachment;
45   
46    /**
47    * Generate {@link MimeMessage}s from {@link WatchListMessageData}s extracted by an iterator over a list of subscribers.
48    *
49    * @version $Id: 88551a662ab5d8ad0189e6f37d39b1ebeb379857 $
50    * @since 7.1M1
51    */
 
52    public class WatchListEventMimeMessageIterator implements Iterator<MimeMessage>, Iterable<MimeMessage>
53    {
54    /**
55    * XWiki User Class first name property name.
56    */
57    public static final String XWIKI_USER_CLASS_FIRST_NAME_PROP = "first_name";
58   
59    /**
60    * XWiki User Class last name property name.
61    */
62    public static final String XWIKI_USER_CLASS_LAST_NAME_PROP = "last_name";
63   
64    /**
65    * Subscriber reference.
66    */
67    public static final String SUBSCRIBER_REFERENCE = "subscriberReference";
68   
69    /**
70    * Template factory "attachments" parameter.
71    */
72    public static final String TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER = "attachments";
73   
74    private MimeMessageFactory<MimeMessage> factory;
75   
76    private Iterator<WatchListMessageData> subscriberIterator;
77   
78    private Map<String, Object> parameters;
79   
80    private Map<String, Object> factoryParameters;
81   
82    private UserAvatarAttachmentExtractor avatarExtractor;
83   
84    private List<Attachment> originalTemplateExtraParameters;
85   
86    private EntityReferenceSerializer<String> serializer;
87   
88    private SessionFactory sessionFactory;
89   
90    /**
91    * @param subscriberIterator the iterator used to go through each subscriber and extract the
92    * {@link WatchListMessageData}
93    * @param factory the factory to use to create a single MimeMessage
94    * @param parameters the parameters from which to extract the factory source and factory parameters
95    * @param avatarExtractor the {@link UserAvatarAttachmentExtractor} used once per message to extract an author's
96    * avatar attachment
97    * @param serializer the {@link EntityReferenceSerializer} used to determine the mail's Message-ID
98    * @param sessionFactory the session factory to be looked at when computing conversation IDs
99    */
 
100  30 toggle public WatchListEventMimeMessageIterator(Iterator<WatchListMessageData> subscriberIterator,
101    MimeMessageFactory<MimeMessage> factory, Map<String, Object> parameters,
102    UserAvatarAttachmentExtractor avatarExtractor, EntityReferenceSerializer<String> serializer,
103    SessionFactory sessionFactory)
104    {
105  30 this.subscriberIterator = subscriberIterator;
106  30 this.factory = factory;
107  30 this.parameters = parameters;
108  30 this.avatarExtractor = avatarExtractor;
109  30 this.serializer = serializer;
110  30 this.sessionFactory = sessionFactory;
111   
112  30 this.factoryParameters =
113    (Map<String, Object>) this.parameters.get(WatchListEventMimeMessageFactory.PARAMETERS_PARAMETER);
114   
115    // Save the list of attachments initially provided by the caller, since we will be constantly updating the
116    // template factory's parameters for each message and we want to remember these to apply them on each message.
117  30 this.originalTemplateExtraParameters =
118    (List<Attachment>) this.factoryParameters.get(TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER);
119    }
120   
 
121  0 toggle @Override
122    public Iterator<MimeMessage> iterator()
123    {
124  0 return this;
125    }
126   
 
127  32 toggle @Override
128    public boolean hasNext()
129    {
130  32 return this.subscriberIterator.hasNext();
131    }
132   
 
133  2 toggle @Override
134    public MimeMessage next()
135    {
136  2 MimeMessage message;
137  2 WatchListMessageData watchListMessageData = this.subscriberIterator.next();
138   
139  2 try {
140    // Update the values for this new message.
141  2 updateFactoryParameters(factoryParameters, watchListMessageData);
142   
143  2 DocumentReference factorySource = watchListMessageData.getTemplateReference();
144   
145    // Use the factory to create the message.
146  2 message = this.factory.createMessage(factorySource, factoryParameters);
147  2 message.addRecipient(Message.RecipientType.TO, watchListMessageData.getAddress());
148   
149    // Set conversation headers.
150  2 message = setConversationHeaders(message, watchListMessageData);
151    } catch (MessagingException e) {
152  0 throw new RuntimeException("Failed to create Mime Message, aborting mail sending for this batch", e);
153    }
154   
155  2 return message;
156    }
157   
158    /**
159    * Update the factory's parameters with the values specific to the message we are going to send right now.
160    *
161    * @param watchListMessageData the messageData to use for the new message
162    * @param factoryParameters the factory parameters we wish to update
163    */
 
164  2 toggle private void updateFactoryParameters(Map<String, Object> factoryParameters,
165    WatchListMessageData watchListMessageData)
166    {
167  2 Map<String, Object> velocityVariables = (Map<String, Object>) factoryParameters.get("velocityVariables");
168   
169    // Set the list of events, containing 1 event per document.
170  2 List<WatchListEvent> events = watchListMessageData.getEvents();
171  2 velocityVariables.put("events", events);
172   
173    // Compute the list of modified documents.
174  2 List<String> modifiedDocuments = new ArrayList<>();
175  2 for (WatchListEvent event : events) {
176  3 if (!modifiedDocuments.contains(event.getPrefixedFullName())) {
177  3 modifiedDocuments.add(event.getPrefixedFullName());
178    }
179    }
180  2 velocityVariables.put("modifiedDocuments", modifiedDocuments);
181   
182  2 velocityVariables.put(XWIKI_USER_CLASS_FIRST_NAME_PROP, watchListMessageData.getFirstName());
183  2 velocityVariables.put(XWIKI_USER_CLASS_LAST_NAME_PROP, watchListMessageData.getLastName());
184  2 velocityVariables.put(SUBSCRIBER_REFERENCE, watchListMessageData.getUserReference());
185   
186    // Attach the avatars of the authors of the events we are notifying about.
187  2 if (parameters.get(WatchListEventMimeMessageFactory.ATTACH_AUTHOR_AVATARS_PARAMETER) == Boolean.TRUE) {
188  1 List<Attachment> templateExtraAttachments = getTemplateExtraAttachments(factoryParameters, events);
189  1 factoryParameters.put(TEMPLATE_FACTORY_ATTACHMENTS_PARAMETER, templateExtraAttachments);
190    }
191   
192    }
193   
 
194  1 toggle private List<Attachment> getTemplateExtraAttachments(Map<String, Object> factoryParameters,
195    List<WatchListEvent> events)
196    {
197    // Append to any existing list of extra attachments specified by the caller.
198  1 List<Attachment> templateExtraAttachments = new ArrayList<>();
199  1 if (originalTemplateExtraParameters != null) {
200  0 templateExtraAttachments.addAll(originalTemplateExtraParameters);
201    }
202   
203  1 Set<DocumentReference> processedAuthors = new HashSet<DocumentReference>();
204  1 for (WatchListEvent event : events) {
205  1 for (DocumentReference authorReference : event.getAuthorReferences()) {
206    // TODO: We do a minimal performance improvement here by not extracting a user's avatar twice for the
207    // same message, but it should probably be the UserAvatarExtractor's job to support some caching
208    // instead since that would also work across messages and would be much more useful.
209  1 if (!processedAuthors.contains(authorReference)) {
210  1 Attachment avatarAttachment = avatarExtractor.getUserAvatar(authorReference);
211  1 if (avatarAttachment != null) {
212  1 templateExtraAttachments.add(avatarAttachment);
213    }
214  1 processedAuthors.add(authorReference);
215    }
216    }
217    }
218  1 return templateExtraAttachments;
219    }
220   
 
221  2 toggle private MimeMessage setConversationHeaders(MimeMessage originalMessage, WatchListMessageData watchListMessageData)
222    throws MessagingException
223    {
224    // We need to copy the message in order to be able to set the Message-ID header without JavaMail overriding
225    // it with a random one by default.
226  2 MimeMessage result = new MimeMessage(originalMessage);
227   
228  2 DocumentReference documentReference = watchListMessageData.getEvents().get(0).getDocumentReference();
229  2 String serializedReference = serializer.serialize(documentReference);
230   
231    // Using MD5 instead of the document reference string to avoid escaping issues. Also, we can not use hashcode()
232    // on document reference because it is not consistent across JVM restarts. hashcode() over the reference string
233    // would also be subject to many collisions, so it's not a good option either.
234  2 String conversationIDPart = DigestUtils.md5Hex(serializedReference);
235  2 String suffix = getConversationSuffix();
236  2 String conversationID = String.format("<%s.XWiki.%s>", conversationIDPart, suffix);
237   
238    // Set the headers.
239  2 result.setHeader("References", conversationID);
240  2 result.setHeader("In-Reply-To", conversationID);
241   
242  2 return result;
243    }
244   
245    /**
246    * Compute the suffix of a conversation ID. It is done similar to what JavaMail does by default, using the session
247    * to extract data (user, host, etc.) that can be set by the client, in case multiple instances of XWiki run on the
248    * same machine.
249    */
 
250  2 toggle private String getConversationSuffix()
251    {
252  2 String suffix = null;
253   
254  2 Session session = this.sessionFactory.create(Collections.<String, String>emptyMap());
255  2 InternetAddress addr = InternetAddress.getLocalAddress(session);
256  2 if (addr != null) {
257  2 suffix = addr.getAddress();
258    } else {
259    // Worst-case default
260  0 suffix = "xwiki@localhost";
261    }
262   
263  2 return suffix;
264    }
265   
 
266  0 toggle @Override
267    public void remove()
268    {
269  0 throw new UnsupportedOperationException("remove");
270    }
271    }